Linux必知必会,答应我拿下这些Linux必备技能(1)

简介: Linux必知必会,答应我拿下这些Linux必备技能(1)

文件IO相关系统调用 (Linux下一切皆文件, 理解掌握文件IO是必须)

  • IO系统调用内核态, 底层数据结构理解助学
  • 我们调用系统调用, 是向内核中对应打开的文件中写入数据, 或者从中读取数据的.  系统调用相当于是打通用户态和内核态的一个通道.

   我们可以通过向文件描述符fd 进行写入数据, 和读取数据. why? fd: 句柄, 内核数据结构进行了完善的封装组织, 我们通过简单的操作fd, 系统调用就会将操作对应映射到对应打开的文件上面去.                              ---   Linux下面一切皆为文件思想贯穿整个Linux的底层设计,  掌握清楚了文件IO, 对于后序的各种通信的学习和理解也是至关重要的

inode节点中存储着文件的属性等各种文件相关重要信息., 名字,权限等信息,每一个文件都会一定一个inode, 这个的理解不易细说, 想要解释清楚,还需要结合文件系统,来理解学习.

  • open

功能: open file  and create new fd (lowest-numbered file descriptor)

Rerturn Val :

sucess return fd           failure return -1

close  

int close(int fd);//关闭fd

括号式编程, 有open 就一定需要close

代码测试

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#define BUFFSIZE 1024
int main(int argc, char* argv[]) {
  if (argc != 2) {
      fprintf(stderr, "usage:%s <filepath>", argv[0]);
      return -1;
  }
  char buff[BUFFSIZE] = {0};//用户态缓冲区
  int fd = open("./a.txt", O_RDONLY);
  if (fd == -1) {
      perror("error open");
      return -2;
  } 
  printf("open file sucess and fd is %d\n", fd);
  close(fd);
  return 0;
}

  • ead

eg:  从a.txt 中读取所有数据.   如下是准备a.txt数据

[tangyujie@VM-4-9-centos IO]$ echo 'Hello Linux IO' > a.txt
[tangyujie@VM-4-9-centos IO]$ cat a.txt
Hello Linux IO

如下是代码实现:

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#define BUFFSIZE 1024
int main(int argc, char* argv[]) {
  if (argc != 2) {
      fprintf(stderr, "usage:%s <filepath>", argv[0]);
      return -1;
  }
  char buff[BUFFSIZE] = {0};//用户态缓冲区
  int fd = open("./a.txt", O_RDONLY);
  if (fd == -1) {
      perror("error open");
      return -2;
  } 
  printf("open file sucess and fd is %d\n", fd);
  int read_size = 0;
  while ((read_size = read(fd, buff, BUFFSIZE)) != 0) {
      buff[read_size] = 0;
      printf("%s", buff);
  }
  close(fd);
  return 0;
}

  • 其实可以稍作修改不再需要带上./      
  • 解释一下为啥我们运行系统命令cat cp ... 不需要./ ?  因为环境变量PATH中存在他们所在路径可以找到这个可执行文件进行执行, 如果我们自己写的可执行程序也想要这样执行, 我们就需要将其路径加入到PATH中 或者 是 将其加入到/user/bin 下面去
  1. 1将其放入到 user/bin/ 目录下面
[tangyujie@VM-4-9-centos IO]$ mycat a.txt
open file sucess and fd is 3
Hello Linux IO
  1. 2将可执行文件所在路径加入到PATH环境变量中去
[tangyujie@VM-4-9-centos IO]$ export PATH=/home/tangyujie/IO:${PATH}
[tangyujie@VM-4-9-centos IO]$ echo ${PATH}
/home/tangyujie/IO:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tangyujie/.local/bin:/home/tangyujie/bin
[tangyujie@VM-4-9-centos IO]$ mycat a.txt
open file sucess and fd is 3
Hello Linux IO
  • write

作用: 向对应的fd打开的文件中写入数据   fd  --->  file*    ---->  file.inode

测试代码: 修改上述mycat 案例中的printf 为 write:

test code

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#define BUFFSIZE 1024
int main(int argc, char* argv[]) {
  if (argc != 2) {
      fprintf(stderr, "usage:%s <filepath>", argv[0]);
      return -1;
  }
  char buff[BUFFSIZE] = {0};//用户态缓冲区
  int fd = open("./a.txt", O_RDONLY);
  if (fd == -1) {
      perror("error open");
      return -2;
  } 
  printf("open file sucess and fd is %d\n", fd);
  int read_size = 0;
  while ((read_size = read(fd, buff, BUFFSIZE)) != 0) {
      write(1, buff, read_size);
  }
  close(fd);
  return 0;
}

test ans

[tangyujie@VM-4-9-centos IO]$ export PATH=/home/tangyujie/IO:${PATH}
[tangyujie@VM-4-9-centos IO]$ echo ${PATH}
/home/tangyujie/IO:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tangyujie/.local/bin:/home/tangyujie/bin
[tangyujie@VM-4-9-centos IO]$ mycat a.txt
open file sucess and fd is 3
Hello Linux IO

fstat  

作用:获取file inode 信息, 文件地各种具体信息.

eg : 获取文件地size信息

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "%s <filepath>", argv[0]);
    return -1;
  }
  int fd = open("./a.txt", O_RDONLY);
  struct stat st;
  int ret = fstat(fd, &st);
  printf("file size : %d\n", st.st_size);
  close(fd);
  return 0;
}
  • fcntl

功能:fcntl() 对打开的文件描述符fd执行下面描述的操作之一。操作由cmd决定。

案例测试, 利用fcntl 设置fd = 0为非阻塞, 然后实现一个简单的轮询机制   (flag | O_NONBLOCK)

案例功能:  在20s中不断地轮询是否有标准输入, 有就输出

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#define BUFFSIZE 512
int main() {
  //设置标准输入为非阻塞
  int flags = -1;
  if (-1 == (flags = fcntl(0, F_GETFL, 0))) {
    perror("fcntl");
    return -1;
  }
  if (-1 == fcntl(0, F_SETFL, flags | O_NONBLOCK)) {
    perror("fcntl");
    return -2;
  }
  char buff[BUFFSIZE] = {0};
  int n = 20;//轮询20s
  while (n > 0) { //轮询
    int read_size = read(0, buff, BUFFSIZE);
    if (read_size >= 0) {
      write(1, buff, read_size);
        continue;
    }
    if (errno == EAGAIN) {
      write(1, "try try\n", 8);
    } else {
            break;//出错了
        }
    sleep(1);//休眠
    n -= 1;
  } 
  return 0;
}
  • dup2 

  • dup2(oldfd, newfd);  函数功能:使 newfd 指向 oldfd 所对应打开地文件

Return Value :

Success  return new descriptor,

Failure return -1;

eg :   简简单单地进行一下 dup(3, 1);   // 这样进行标准输出, 就会输出到 open file中去了, test 案例, 其实这个就是一个输出重定向了

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "%s <filepath>", argv[0]);
    return -1;
  }
  int fd = open("./a.txt", O_RDWR);
  //其实fd == 3;
    int ret = dup2(fd, 1);  //进行输出重定向到a.txt文件中
    if (ret == -1) {
        perror("dup2");
        return -2;
    }
  write(1, "Hello Dup2\n", 11);
    char buff[12];
    sprintf(buff, "%d", ret);
    write(1, buff, strlen(buff));
  close(fd);
  close(ret);
  return 0;
}

理解重定向

>   >>    <   这些都是重定向符号, 上述, 本来该输出到显示器上地数据全部输出到open file a.txt中了, 这种就叫做重定向.

[tangyujie@VM-4-9-centos IO]$ echo "Hello 重定向" > a.txt
[tangyujie@VM-4-9-centos IO]$ cat a.txt
Hello 重定向
[tangyujie@VM-4-9-centos IO]$ echo "Hello 没有重定向, 正常输出到显示屏上"
Hello 没有重定向, 正常输出到显示屏上

echo "Hello 重定向" 本来正常应该输出到显示屏幕上地, 然后我们进行一下 > a.txt, 本应该正常输出到显示屏上地数据就写入到了a.txt文件中了, 真有意思哈

重定向理解:本来应该输出到标准显示器设备上去地数据输出到重定向地文件中, 本来应该从标准输入设备键盘上读取地数据, 从定向从文件中读取了...

其实重定向在底层就做了两件事:close(0) close(1)   然后  open(定向文件).

文件IO小结:  提问解答, 巩固提升


每一个进程地task_struct如何关联文件?        files*成员指针 指向  files_struct.

fd文件描述符本质是什么?                               是file* 数组地下标 fd_array下标

file 如何获取文件各种信息?                            file中存在inode元信息

fstat有啥作用?                                                可以获取文件各种信息

重定向本质是什么?                                         fd重新对应一个open file                        

dup2(oldfd, newfd)如何理解?                       newfd对应指向oldfd的inode

进程调度相关系统调用

  • 进程理解

  • 上述这张图, 几乎每一个初学进程的人都会认识的第一张图.  
  • 画图软件, 浏览器, 各种软件, 双击点开,究竟是干了一件什么事情?  
  1. 将文件从硬盘加载到内存中
  2. 然后CPU不断的从内存中获取指令  +  数据进行运算    
  3. 将运算的结果写回内存. (小杰盲猜是写到显存, 显卡内存) 不然你咋可以在电脑屏幕上看见效果嘞
  4. 上述的处于运行状态下的程序就是一个进程了.   程序仅仅只是一段代码, 是静态的. 是一些数据 + 指令所构成的.
  5. 进程:运行起来的程序, 是动态的.  

进程:是系统资源分配的基本单位. (分配CPU 内存资源...).

PCB:进程控制块, 进程映射到内核中的数据封装, 数据结构, 管理着当前操作系统下运行的所有程序.              在Linux下的 PCB 实例 叫做  task_struct

task_struct 结构中所包含的重要信息

pid : 进程id号, 进程的唯一标识, 类比身份证号

进程状态信息: running 运行    waiting stop  挂起等待  zombie  僵尸

files*  指向一个  files_struct, files_struct 核心是包含一个file* 指针数组. 数组下标就是fd

程序计数器PC指针, 指向下一条指令的地址

优先级信息.process 进程状态理解:


运行状态 :   进程处在运行队列的队头, CPU正在处理其中的指令进行运算刷新结果


等待状态 :   进程处在等待队列中,  整个进程休眠, 不分配CPU, 进程等待被唤醒, 存在很多进程间切换,代价不小


僵尸状态: 进程死亡了, 结束了, 但是尸体摊在哪里, 无人收尸, 故而成为僵尸, 正常来说, 进程终止运行之后会被父进程或者是操作系统回收资源.  终止后 资源得不到回收的进程  便称为 僵尸进程


等下后面我会在合适的位置为大家演示一下何为僵尸    --- 至此大家先对于僵尸的理解浅止于尸体,系统资源得不到回收的状态.


进程重点性质学习


独立性: 多个进程的进程地址空间是隔离的,进程是独占进程资源的, 多进程之间运行互不干扰


并行:在多核CPU作用下, 多个进程同时运行,同时推进, 谓之并行


并发:多个进程在一个CPU下,在一段时间内, 不停的进行进程间切换, 使得多个进程在这一段时间中都向前推进, 谓之并发.                      (表面上看多个任务,进程都在执行, 其实是切换着使用单CPU执行, 同一时刻仅仅只是一个进程得以运行, 但是一段时间内, 由于切换执行, 都有所推进)

  • fork

功能:复制一个子进程出来.     -- 注意词语: 复制


此处我为何要用复制, 而不是创建等其他词语, 我说复制, 其实就是想告诉大家 fork 出来的子进程几乎是和原来的进程是一摸一样的, 不同仅仅只是 pid ... 些许的差别,     就像区分克隆体只能通过编号一般    


fork之后, 两个进程是运行一样的代码. 相当于是在fork处进行一个分流出来一样, 都是执行相同的代码块,  但是父子往往逻辑分工不同,    此时 我们需要通过一定的判断, 区分父进程和子进程, 使其执行不同的代码逻辑.            --- 各自分工.

卖个关子,留个疑惑, 我们如何通过判断区分父子进程?      先看代码   --- 父进程打印父进程pid说我是爸爸,  子进程打印子进程pid说我是儿子.

#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
  pid_t pid;
  pid = fork();
  if (pid == 0) {  // 说明是son 
    printf("return val: %d sonpid: %d, I am son\n", pid, getpid());
        sleep(1);//睡一下等儿子先死掉.
  } else {
    //说明是 fa
    printf("return val: %d, pid: %d\n", getpid(), pid);
  }
  return 0;
}

---  透过表象看见了啥了?    发现    fork 之后的 return val 不一样呀我去


其实精华出现了:       好比是开返回值盲盒.  开到了0 OK 我知道了,我是儿子, 开到了非0 我知道了,我开出来了我儿子的进程id号, 我是爸爸.


Return Val :


对于父进程 :  return sonpid. 子进程id号


对于子进程 :   return 0.

好了好了, 搞定了fork了, 咱就可以演示一把僵尸了.       ---  咋个玩?   父进程死睡, 子进程自杀, 就可以产生僵尸,   因为子进程死了, 但是我父进程是不停的循环着睡觉, 根本睡不醒, 实在没法给孩子收尸, 孩子尸体晾在哪里, 就是僵尸进程

#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
  pid_t pid;
  pid = fork();
  if (pid == 0) {
    return -1;//子进程直接死掉嗝屁
  } else {
    //fa
    while (1) {
      sleep(1);//不停睡, 睡醒再睡
    }
  }
  return 0;
}

爸爸死睡觉, 儿子躺板板成僵尸了

  • wait + waitpid

传出参数status, 作用就是传出获取死亡状态信息. 其实就是获取退出码.

status的实现, 本质是啥?    其实我们应该看成是二进制位, 看成是32位二进制位, 但是我们仅仅只是研究低16位.    底16位的高八位存储的是正常退出的退出码, 低七位标识是否是正常退出的, 也就是信号码 sig code.            status & 0x7F 就是 sig code. 终止信号值

多说无益, 代码才是硬道理,  情景案例: 正常杀死   +  使用 kill -9 杀死, 咱看看究竟  exit code 和 sig code 是不是对应的值就OK了      

WIFEXITED(status)           判断是否是正常退出

        returns true if the child terminated normally,


WEXITSTATUS(status)      返回孩子的退出状态

        returns the exit status of the child


WIFSIGNALED(status)       判断是不是信号异常终止

        returns true if the child process was terminated by a signal.


WTERMSIG(status)            返回终止信号

        returns the number of the signal that caused the child process to terminate.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
  pid_t pid;
  pid = fork();//创建copy进程
  if (pid == 0) {
    //son process
    sleep(30);//子进程先进行休眠, 等待被kill
    exit(100);//正常退出, 死亡状态码100
  } else {
    int status;//获取死亡状态信息
    if (-1 == wait(&status)) {
      perror("wait");
      return -2;
    }
    //通过低7位字节判断是不是信号杀死的
    if (status & 0x7F) {
      //true 是信号杀死的
      printf("process is exit by signal, and signal code is %d\n", status & 0x7F);
    } else {
      //false 正常退出的
      printf("process is exited, and exit code is %d\n", (status>>8) & 0xFF);
      //低16位的高八位是正常退出时候的退出码
    }
  }
  return 0;
}

ans: 正常退出, 果然是   status的低16位的高8位就是存储的正常退出码.

ans: 被kill -9 信号杀死     status的低16位的低7位就是存储的中断信号

在休眠时间内, 将子进程给kill -9 暗杀了

方式2:通过提供的系统调用进行判断 , 其实系统调用底层也是按照二进制位进行  &  操作来获取的死亡状态信息的

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
  pid_t pid;
  pid = fork();//创建copy进程
  if (pid == 0) {
    //son process
    sleep(30);//子进程先进行休眠, 等待被kill
    exit(100);//正常退出, 死亡状态码100
  } else {
    int status;//获取死亡状态信息
    if (-1 == wait(&status)) {
      perror("wait");
      return -2;
    }
    if (WIFEXITED(status)) {
      //说明是正常中断的
      printf("process is exited, and exit code is %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
      //说明是信号中断掉的
      printf("process is signal, and signal code is %d\n", WTERMSIG(status));
    } else {
      printf("进程终止原因未知\n");
    }
  }
  return 0;
}
  • execvp
  • 程序替换, 将进程中的程序偷换为其他应用程序进行执行, 借进程地址空间,偷换程序执行其他程序.            

exec 前后 pid 是不变的, 意思是说进程还是同一个进程, 但是进程执行的代码和数据都换了个遍

至此:理解一些东西, 我们在Linux下执行的ls , cat, pstree ... 命令, 过程究竟如何?

为啥要先  fork 再 exec执行相应的命令, 不然你以为直接exec 直接在bash 本体上干, 干完之后原来的bash 还在嘛, 所以自然是fork出来一个克隆体去进行替换执行


exec一组存在很多的封装库函数, 有各式各样的, 但是其实我个人认为最好用, 最简单的就是execvp了, exec簇函数, 都是execve系统调用的封装库函数, 作用也都是进行进程替换, 替换进程执行部的代码和数据, 让fork出来的进程去执行其他的程序

函数簇特征拆解:

l (list):列表, 参数列表, 意思就是参数通过可变参数列表args传入, 也可以理解为参数包

p (PATH) : 意思在于说带有p 就可以在PATH环境变量中自动查找路径, 执行程序的时候就不需要手动传入路径path

v(vector) : 表示参数用数组来传入, 数组末尾存储的是NULL

e(env): 是否需要传入环境变量数组. envp[]

使用execvp来使用子进程来完成一下ls -al命令的执行看看

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
  pid_t pid = fork();
  if (pid == 0) {
    //son process
    const char* args[] = {"ls", "-al", NULL};
    execvp("ls", args);
  } else {
    //fa process 直接进行exit
    exit(10);
  }
  return 0;
}

execvp(const char* file, const char* argv[]);    


file : 指的是可执行程序的文件名


argv : 参数包, 就是我们正常写 ls -al 这样的参数包数组,   没有指定大小, 所以需要一个结束字符, 将结束设置为NULL, 前面的 依次按照我们在执行命令时候写的参数依次拆解成字符串写入即可


eg : ls -al     args[] = {"ls", "-al", NULL}       ps -aux    args[]  = {"ps", "-aux",  NULL}

进程理解,调度,提问解答, 巩固提升


进程是什么?                             运行中的程序, CPU从内存中获取二进制指令 + 数据执行中的程序.

PCB是什么?                             进程控制块, 进程的组织管理的数据结构

进程有哪些状态, 如何理解?      running:   运行态, 占据CPU执行的状态      ready: 除了CPU其他一切资源都准备好的状态, 只欠CPU    sleeping:  阻塞休眠挂起等待状态, 一般是被阻塞函数阻塞挂起来了    zombie:  僵尸, 进程终止, 但是资源尸体得不到回收的状态

进程调度是啥?                          分配CPU, 开始执行, 就是进程调度起来了. 从就绪状态转换到运行态, 按照一定的调度算法从就绪队列中选取合适的进程进行调度

进程的独立性是什么?               用户空间, 进程地址空间相互独立, 进程间互不干扰          

并行性?                                     在多核CPU作用下, 多个进程同时执行, 向前推进

并发性?                                     单个CPU作用下, 多个进程切换执行, 一段时间内都向前推进

fork进程究竟如何叫合适?        创建一个克隆进程, 区分的关键在于return val.

ls命令执行的真相?                    bash 先 fork 出来一个 克隆bash 再进行exec 执行ls程序  


相关文章
|
消息中间件 存储 缓存
Linux必知必会,答应我拿下这些Linux必备技能(2)
Linux必知必会,答应我拿下这些Linux必备技能(2)
Linux必知必会,答应我拿下这些Linux必备技能(2)
|
网络协议 Linux 数据安全/隐私保护
LINUX命令必备技能
LINUX命令必备技能
90 0
|
运维 供应链 Cloud Native
报名中!阿里云、统信软件、西安邮电等多位专家教授畅谈eBPF和Linux的硬核技能|2022云栖大会
12场技术分享+3大产品动手实践,本专场邀请了创业公司、高校师生老师和学生等多视角 KOL一起分享。
报名中!阿里云、统信软件、西安邮电等多位专家教授畅谈eBPF和Linux的硬核技能|2022云栖大会
|
Linux
2000字教你如何玩转Linux man命令,隐藏技能非常nice!
man命令对于日常使用linux的朋友非常友好,提供了不少好用的参数,配上参数简直就是一把利器,除了我举了例子的参数,还有好多好用的参数等着大家去开发,大家可以在使用的时候直接man man查看有哪些自己能够利用上的参数哦!
190 0
2000字教你如何玩转Linux man命令,隐藏技能非常nice!
|
Linux 应用服务中间件 开发工具
Linux必备技能:如何在Vim中跳到文件的开头或者结尾?
今天给大家带来的是Linux方面的小实战:如何在Vim中跳到文件的开头或者结尾?
960 1
Linux必备技能:如何在Vim中跳到文件的开头或者结尾?
|
安全 网络协议 Ubuntu
[CentOS,LISTEN,运行级别,NFS,Debian]强化Linux安全的10个技能
  1.找出不必要的服务   很明显,服务器上跑的服务,并不是每个都有用的。强烈建议检查并关掉不需要的服务,从而减少风险(多跑一个服务,就可能多几个漏洞)。   查询运行在runlevel 3的服务列表:   [afei@afei ~]# /sbin/chkconfig --list |grep '3:on'
125 0
|
网络协议 Linux 数据安全/隐私保护
LINUX命令必备技能
LINUX命令必备技能
108 0
|
关系型数据库 Shell 应用服务中间件