• Linux多进程开发(二)


    Linux多进程开发(二)

    视频题目:牛客网c++高薪项目

    视频链接:https://www.nowcoder.com/study/live/504

    进程概述

    程序和进程

     

     单道、多道程序设计

    时间片

     并行和并发

     

     

    进程控制块(PCB)

     

     

    进程状态转换

    进程的状态

     

     进程相关命令

     

     

    ./a.out & :程序在后台运行,输出也可以打印在前台上面。

    进程号和相关函数

    进程创建

    进程创建

    fork()读时共享,写时子进程才copy复制一份程序和虚拟地址空间,使得当父进程写的时候改变了物理地址,但是子进程指向的还是原来的物理空间指向的值,两者的物理空间指向的值可能不同。

    父子进程关系和GDB多进程调试

     

    (面试常考)GDB多进程调试

    exec函数族

    exec函数族介绍

     exec函数族作用图解

     

     

    代码:

     1 /*  
     2     #include <unistd.h>
     3     int execl(const char *path, const char *arg, ...);
     4         - 参数:
     5             - path:需要指定的执行的文件的路径或者名称
     6                 a.out /home/nowcoder/a.out 推荐使用绝对路径
     7                 ./a.out hello world
     8 
     9             - arg:是执行可执行文件所需要的参数列表
    10                 第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
    11                 从第二个参数开始往后,就是程序执行所需要的的参数列表。
    12                 参数最后需要以NULL结束(哨兵)
    13 
    14         - 返回值:
    15             只有当调用失败,才会有返回值,返回-1,并且设置errno
    16             如果调用成功,没有返回值。
    17 
    18 */
    19 #include <unistd.h>
    20 #include <stdio.h>
    21 
    22 int main() {
    23 
    24 
    25     // 创建一个子进程,在子进程中执行exec函数族中的函数
    26     pid_t pid = fork();
    27 
    28     if(pid > 0) {
    29         // 父进程
    30         printf("i am parent process, pid : %d\n",getpid());
    31         sleep(1);
    32     }else if(pid == 0) {
    33         // 子进程
    34         // execl("hello","hello",NULL);
    35 
    36         execl("/bin/ps", "ps", "aux", NULL);
    37         perror("execl");
    38         printf("i am child process, pid : %d\n", getpid());
    39 
    40     }
    41 
    42     for(int i = 0; i < 3; i++) {
    43         printf("i = %d, pid = %d\n", i, getpid());
    44     }
    45 
    46 
    47     return 0;
    48 }
     1 /*  
     2     #include <unistd.h>
     3     int execlp(const char *file, const char *arg, ... );
     4         - 会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
     5         - 参数:
     6             - file:需要执行的可执行文件的文件名
     7                 a.out
     8                 ps
     9 
    10             - arg:是执行可执行文件所需要的参数列表
    11                 第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
    12                 从第二个参数开始往后,就是程序执行所需要的的参数列表。
    13                 参数最后需要以NULL结束(哨兵)
    14 
    15         - 返回值:
    16             只有当调用失败,才会有返回值,返回-1,并且设置errno
    17             如果调用成功,没有返回值。
    18 
    19 
    20         int execv(const char *path, char *const argv[]);
    21         argv是需要的参数的一个字符串数组
    22         char * argv[] = {"ps", "aux", NULL};
    23         execv("/bin/ps", argv);
    24 
    25         int execve(const char *filename, char *const argv[], char *const envp[]);
    26         char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};
    27 
    28 
    29 */
    30 #include <unistd.h>
    31 #include <stdio.h>
    32 
    33 int main() {
    34 
    35 
    36     // 创建一个子进程,在子进程中执行exec函数族中的函数
    37     pid_t pid = fork();
    38 
    39     if(pid > 0) {
    40         // 父进程
    41         printf("i am parent process, pid : %d\n",getpid());
    42         sleep(1);
    43     }else if(pid == 0) {
    44         // 子进程
    45         execlp("ps", "ps", "aux", NULL);
    46 
    47         printf("i am child process, pid : %d\n", getpid());
    48 
    49     }
    50 
    51     for(int i = 0; i < 3; i++) {
    52         printf("i = %d, pid = %d\n", i, getpid());
    53     }
    54 
    55 
    56     return 0;
    57 }
    1 #include <stdio.h>
    2 
    3 int main() {    
    4 
    5     printf("hello, world\n");
    6 
    7     return 0;
    8 }

    进程退出、孤儿进程、僵尸进程

    进程退出

     孤儿进程

     僵尸进程

    代码

    exit函数

     1 /*
     2     #include <stdlib.h>
     3     void exit(int status);
     4 
     5     #include <unistd.h>
     6     void _exit(int status);
     7 
     8     status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。
     9 */
    10 #include <stdio.h>
    11 #include <stdlib.h>
    12 #include <unistd.h>
    13 
    14 int main() {
    15 
    16     printf("hello\n");
    17     printf("world");
    18 
    19     // exit(0);
    20     _exit(0);
    21     
    22     return 0;
    23 }

    孤儿进程

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 
     5 int main() {
     6 
     7     // 创建子进程
     8     pid_t pid = fork();
     9 
    10     // 判断是父进程还是子进程
    11     if(pid > 0) {
    12 
    13         printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
    14 
    15     } else if(pid == 0) {
    16         sleep(1);
    17         // 当前是子进程
    18         printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
    19        
    20     }
    21 
    22     // for循环
    23     for(int i = 0; i < 3; i++) {
    24         printf("i : %d , pid : %d\n", i , getpid());
    25     }
    26 
    27     return 0;
    28 }

    僵尸进程

     1 #include <sys/types.h>
     2 #include <unistd.h>
     3 #include <stdio.h>
     4 
     5 int main() {
     6 
     7     // 创建子进程
     8     pid_t pid = fork();
     9 
    10     // 判断是父进程还是子进程
    11     if(pid > 0) {
    12         while(1) {
    13             printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
    14             sleep(1);
    15         }
    16 
    17     } else if(pid == 0) {
    18         // 当前是子进程
    19         printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
    20        
    21     }
    22 
    23     // for循环
    24     for(int i = 0; i < 3; i++) {
    25         printf("i : %d , pid : %d\n", i , getpid());
    26     }
    27 
    28     return 0;
    29 }

    wait函数

    进程回收

     退出信息相关宏函数

     

    waitpid函数

    代码

     1 /*
     2     #include <sys/types.h>
     3     #include <sys/wait.h>
     4     pid_t waitpid(pid_t pid, int *wstatus, int options);
     5         功能:回收指定进程号的子进程,可以设置是否阻塞。
     6         参数:
     7             - pid:
     8                 pid > 0 : 某个子进程的pid
     9                 pid = 0 : 回收当前进程组的所有子进程    
    10                 pid = -1 : 回收所有的子进程,相当于 wait()  (最常用)
    11                 pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程
    12             - options:设置阻塞或者非阻塞
    13                 0 : 阻塞
    14                 WNOHANG : 非阻塞
    15             - 返回值:
    16                 > 0 : 返回子进程的id
    17                 = 0 : options=WNOHANG, 表示还有子进程活着
    18                 = -1 :错误,或者没有子进程了
    19 */
    20 #include <sys/types.h>
    21 #include <sys/wait.h>
    22 #include <stdio.h>
    23 #include <unistd.h>
    24 #include <stdlib.h>
    25 
    26 int main() {
    27 
    28     // 有一个父进程,创建5个子进程(兄弟)
    29     pid_t pid;
    30 
    31     // 创建5个子进程
    32     for(int i = 0; i < 5; i++) {
    33         pid = fork();
    34         if(pid == 0) {
    35             break;
    36         }
    37     }
    38 
    39     if(pid > 0) {
    40         // 父进程
    41         while(1) {
    42             printf("parent, pid = %d\n", getpid());
    43             sleep(1);
    44 
    45             int st;
    46             // int ret = waitpid(-1, &st, 0);
    47             int ret = waitpid(-1, &st, WNOHANG);
    48 
    49             if(ret == -1) {
    50                 break;
    51             } else if(ret == 0) {
    52                 // 说明还有子进程存在
    53                 continue;
    54             } else if(ret > 0) {
    55 
    56                 if(WIFEXITED(st)) {
    57                     // 是不是正常退出
    58                     printf("退出的状态码:%d\n", WEXITSTATUS(st));
    59                 }
    60                 if(WIFSIGNALED(st)) {
    61                     // 是不是异常终止
    62                     printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
    63                 }
    64 
    65                 printf("child die, pid = %d\n", ret);
    66             }
    67            
    68         }
    69 
    70     } else if (pid == 0){
    71         // 子进程
    72          while(1) {
    73             printf("child, pid = %d\n",getpid());    
    74             sleep(1);       
    75          }
    76         exit(0);
    77     }
    78 
    79     return 0; 
    80 }

    进程间通信

    进程间通信概念

    Linux系统进程间通信方式(面试必须背住)

    匿名管道

     管道的特点

     

    为什么可以使用管道进行进程间通信?

    管道的数据结构

    逻辑上是环形的队列,实际数据结构不是环形的。

    匿名管道的使用

     

     

     代码

    pipe.c

     1 /*
     2     #include <unistd.h>
     3     int pipe(int pipefd[2]);
     4         功能:创建一个匿名管道,用来进程间通信。
     5         参数:int pipefd[2] 这个数组是一个传出参数。
     6             pipefd[0] 对应的是管道的读端
     7             pipefd[1] 对应的是管道的写端
     8         返回值:
     9             成功 0
    10             失败 -1
    11 
    12     管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
    13 
    14     注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
    15 */
    16 
    17 // 子进程发送数据给父进程,父进程读取到数据输出
    18 #include <unistd.h>
    19 #include <sys/types.h>
    20 #include <stdio.h>
    21 #include <stdlib.h>
    22 #include <string.h>
    23 
    24 int main() {
    25 
    26     // 在fork之前创建管道
    27     int pipefd[2];
    28     int ret = pipe(pipefd);
    29     if(ret == -1) {
    30         perror("pipe");
    31         exit(0);
    32     }
    33 
    34     // 创建子进程
    35     pid_t pid = fork();
    36     if(pid > 0) {
    37         // 父进程
    38         printf("i am parent process, pid : %d\n", getpid());
    39 
    40         // 关闭写端
    41         close(pipefd[1]);
    42         
    43         // 从管道的读取端读取数据
    44         char buf[1024] = {0};
    45         while(1) {
    46             int len = read(pipefd[0], buf, sizeof(buf));
    47             printf("parent recv : %s, pid : %d\n", buf, getpid());
    48             
    49             // 向管道中写入数据
    50             //char * str = "hello,i am parent";
    51             //write(pipefd[1], str, strlen(str));
    52             //sleep(1);
    53         }
    54 
    55     } else if(pid == 0){
    56         // 子进程
    57         printf("i am child process, pid : %d\n", getpid());
    58         // 关闭读端
    59         close(pipefd[0]);
    60         char buf[1024] = {0};
    61         while(1) {
    62             // 向管道中写入数据
    63             char * str = "hello,i am child";
    64             write(pipefd[1], str, strlen(str));
    65             //sleep(1);
    66 
    67             // int len = read(pipefd[0], buf, sizeof(buf));
    68             // printf("child recv : %s, pid : %d\n", buf, getpid());
    69             // bzero(buf, 1024);
    70         }
    71         
    72     }
    73     return 0;
    74 }

     fpathconf.c

     1 #include <unistd.h>
     2 #include <sys/types.h>
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <string.h>
     6 
     7 int main() {
     8 
     9     int pipefd[2];
    10 
    11     int ret = pipe(pipefd);
    12 
    13     // 获取管道的大小
    14     long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
    15 
    16     printf("pipe size : %ld\n", size);
    17 
    18     return 0;
    19 }

    匿名管道的使用


     

     匿名管道通信案例

    代码:

     1 /*
     2     实现 ps aux | grep xxx 父子进程间通信
     3     
     4     子进程: ps aux, 子进程结束后,将数据发送给父进程
     5     父进程:获取到数据,过滤
     6     pipe()
     7     execlp()
     8     子进程将标准输出 stdout_fileno 重定向到管道的写端。  dup2
     9 */
    10 
    11 #include <unistd.h>
    12 #include <sys/types.h>
    13 #include <stdio.h>
    14 #include <stdlib.h>
    15 #include <string.h>
    16 #include <wait.h>
    17 
    18 int main() {
    19 
    20     // 创建一个管道
    21     int fd[2];
    22     int ret = pipe(fd);
    23 
    24     if(ret == -1) {
    25         perror("pipe");
    26         exit(0);
    27     }
    28 
    29     // 创建子进程
    30     pid_t pid = fork();
    31 
    32     if(pid > 0) {
    33         // 父进程
    34         // 关闭写端
    35         close(fd[1]);
    36         // 从管道中读取
    37         char buf[1024] = {0};
    38 
    39         int len = -1;
    40         while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
    41             // 过滤数据输出
    42             printf("%s", buf);
    43             memset(buf, 0, 1024);
    44         }
    45 
    46         wait(NULL);
    47 
    48     } else if(pid == 0) {
    49         // 子进程
    50         // 关闭读端
    51         close(fd[0]);
    52 
    53         // 文件描述符的重定向 stdout_fileno -> fd[1]
    54         dup2(fd[1], STDOUT_FILENO);
    55         // 执行 ps aux
    56         execlp("ps", "ps", "aux", NULL);
    57         perror("execlp");
    58         exit(0);
    59     } else {
    60         perror("fork");
    61         exit(0);
    62     }
    63 
    64 
    65     return 0;
    66 }

    管道的读写特点和管道设置为非阻塞的

    管道的读写特点

    管道的读写特点:
    使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
    1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
    读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

    2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程
    也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
    再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。

    3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
    向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。

    4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程
    也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,
    直到管道中有空位置才能再次写入数据并返回。

    总结:
    读管道:
    管道中有数据,read返回实际读到的字节数。
    管道中无数据:
    写端被全部关闭,read返回0(相当于读到文件的末尾)
    写端没有完全关闭,read阻塞等待

    写管道:
    管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    管道读端没有全部关闭:
    管道已满,write阻塞
    管道没有满,write将数据写入,并返回实际写入的字节数

    代码

     1 #include <unistd.h>
     2 #include <sys/types.h>
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <string.h>
     6 #include <fcntl.h>
     7 /*
     8     设置管道非阻塞
     9     int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flag
    10     flags |= O_NONBLOCK;            // 修改flag的值
    11     fcntl(fd[0], F_SETFL, flags);   // 设置新的flag
    12 */
    13 int main() {
    14 
    15     // 在fork之前创建管道
    16     int pipefd[2];
    17     int ret = pipe(pipefd);
    18     if(ret == -1) {
    19         perror("pipe");
    20         exit(0);
    21     }
    22 
    23     // 创建子进程
    24     pid_t pid = fork();
    25     if(pid > 0) {
    26         // 父进程
    27         printf("i am parent process, pid : %d\n", getpid());
    28 
    29         // 关闭写端
    30         close(pipefd[1]);
    31         
    32         // 从管道的读取端读取数据
    33         char buf[1024] = {0};
    34 
    35         int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flag
    36         flags |= O_NONBLOCK;            // 修改flag的值
    37         fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flag
    38 
    39         while(1) {
    40             int len = read(pipefd[0], buf, sizeof(buf));
    41             printf("len : %d\n", len);
    42             printf("parent recv : %s, pid : %d\n", buf, getpid());
    43             memset(buf, 0, 1024);
    44             sleep(1);
    45         }
    46 
    47     } else if(pid == 0){
    48         // 子进程
    49         printf("i am child process, pid : %d\n", getpid());
    50         // 关闭读端
    51         close(pipefd[0]);
    52         char buf[1024] = {0};
    53         while(1) {
    54             // 向管道中写入数据
    55             char * str = "hello,i am child";
    56             write(pipefd[1], str, strlen(str));
    57             sleep(5);
    58         }
    59         
    60     }
    61     return 0;
    62 }

    有名管道介绍和使用

    有名管道

     

     有名管道的使用

    代码

    mkfifo.c

     1 /*
     2     创建fifo文件
     3     1.通过命令: mkfifo 名字
     4     2.通过函数:int mkfifo(const char *pathname, mode_t mode);
     5 
     6     #include <sys/types.h>
     7     #include <sys/stat.h>
     8     int mkfifo(const char *pathname, mode_t mode);
     9         参数:
    10             - pathname: 管道名称的路径
    11             - mode: 文件的权限 和 open 的 mode 是一样的
    12                     是一个八进制的数
    13         返回值:成功返回0,失败返回-1,并设置错误号
    14 
    15 */
    16 
    17 #include <stdio.h>
    18 #include <sys/types.h>
    19 #include <sys/stat.h>
    20 #include <stdlib.h>
    21 #include <unistd.h>
    22 
    23 int main() {
    24 
    25 
    26     // 判断文件是否存在
    27     int ret = access("fifo1", F_OK);
    28     if(ret == -1) {
    29         printf("管道不存在,创建管道\n");
    30         
    31         ret = mkfifo("fifo1", 0664);
    32 
    33         if(ret == -1) {
    34             perror("mkfifo");
    35             exit(0);
    36         }       
    37 
    38     }
    39 
    40     
    41 
    42     return 0;
    43 }

    read.c

     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <stdlib.h>
     5 #include <unistd.h>
     6 #include <fcntl.h>
     7 
     8 // 从管道中读取数据
     9 int main() {
    10 
    11     // 1.打开管道文件
    12     int fd = open("test", O_RDONLY);
    13     if(fd == -1) {
    14         perror("open");
    15         exit(0);
    16     }
    17 
    18     // 读数据
    19     while(1) {
    20         char buf[1024] = {0};
    21         int len = read(fd, buf, sizeof(buf));
    22         if(len == 0) {
    23             printf("写端断开连接了...\n");
    24             break;
    25         }
    26         printf("recv buf : %s\n", buf);
    27     }
    28 
    29     close(fd);
    30 
    31     return 0;
    32 }

    write.c

     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <stdlib.h>
     5 #include <unistd.h>
     6 #include <fcntl.h>
     7 #include <string.h>
     8 
     9 // 向管道中写数据
    10 /*
    11     有名管道的注意事项:
    12         1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
    13         2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
    14 
    15     读管道:
    16         管道中有数据,read返回实际读到的字节数
    17         管道中无数据:
    18             管道写端被全部关闭,read返回0,(相当于读到文件末尾)
    19             写端没有全部被关闭,read阻塞等待
    20     
    21     写管道:
    22         管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
    23         管道读端没有全部关闭:
    24             管道已经满了,write会阻塞
    25             管道没有满,write将数据写入,并返回实际写入的字节数。
    26 */
    27 int main() {
    28 
    29     // 1.判断文件是否存在
    30     int ret = access("test", F_OK);
    31     if(ret == -1) {
    32         printf("管道不存在,创建管道\n");
    33         
    34         // 2.创建管道文件
    35         ret = mkfifo("test", 0664);
    36 
    37         if(ret == -1) {
    38             perror("mkfifo");
    39             exit(0);
    40         }       
    41 
    42     }
    43 
    44     // 3.以只写的方式打开管道
    45     int fd = open("test", O_WRONLY);
    46     if(fd == -1) {
    47         perror("open");
    48         exit(0);
    49     }
    50 
    51     // 写数据
    52     for(int i = 0; i < 100; i++) {
    53         char buf[1024];
    54         sprintf(buf, "hello, %d\n", i);
    55         printf("write data : %s\n", buf);
    56         write(fd, buf, strlen(buf));
    57         sleep(1);
    58     }
    59 
    60     close(fd);
    61 
    62     return 0;
    63 }

    有名管道实现简单版聊天功能

    有名管道的使用

     代码

    chatA.c

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <sys/stat.h>
     5 #include <stdlib.h>
     6 #include <fcntl.h>
     7 #include <string.h>
     8 
     9 int main() {
    10 
    11     // 1.判断有名管道文件是否存在
    12     int ret = access("fifo1", F_OK);
    13     if(ret == -1) {
    14         // 文件不存在
    15         printf("管道不存在,创建对应的有名管道\n");
    16         ret = mkfifo("fifo1", 0664);
    17         if(ret == -1) {
    18             perror("mkfifo");
    19             exit(0);
    20         }
    21     }
    22 
    23     ret = access("fifo2", F_OK);
    24     if(ret == -1) {
    25         // 文件不存在
    26         printf("管道不存在,创建对应的有名管道\n");
    27         ret = mkfifo("fifo2", 0664);
    28         if(ret == -1) {
    29             perror("mkfifo");
    30             exit(0);
    31         }
    32     }
    33 
    34     // 2.以只写的方式打开管道fifo1
    35     int fdw = open("fifo1", O_WRONLY);
    36     if(fdw == -1) {
    37         perror("open");
    38         exit(0);
    39     }
    40     printf("打开管道fifo1成功,等待写入...\n");
    41     // 3.以只读的方式打开管道fifo2
    42     int fdr = open("fifo2", O_RDONLY);
    43     if(fdr == -1) {
    44         perror("open");
    45         exit(0);
    46     }
    47     printf("打开管道fifo2成功,等待读取...\n");
    48 
    49     char buf[128];
    50 
    51     // 4.循环的写读数据
    52     while(1) {
    53         memset(buf, 0, 128);
    54         // 获取标准输入的数据
    55         fgets(buf, 128, stdin);
    56         // 写数据
    57         ret = write(fdw, buf, strlen(buf));
    58         if(ret == -1) {
    59             perror("write");
    60             exit(0);
    61         }
    62 
    63         // 5.读管道数据
    64         memset(buf, 0, 128);
    65         ret = read(fdr, buf, 128);
    66         if(ret <= 0) {
    67             perror("read");
    68             break;
    69         }
    70         printf("buf: %s\n", buf);
    71     }
    72 
    73     // 6.关闭文件描述符
    74     close(fdr);
    75     close(fdw);
    76 
    77     return 0;
    78 }

    chatB.c

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <sys/stat.h>
     5 #include <stdlib.h>
     6 #include <fcntl.h>
     7 #include <string.h>
     8 
     9 int main() {
    10 
    11     // 1.判断有名管道文件是否存在
    12     int ret = access("fifo1", F_OK);
    13     if(ret == -1) {
    14         // 文件不存在
    15         printf("管道不存在,创建对应的有名管道\n");
    16         ret = mkfifo("fifo1", 0664);
    17         if(ret == -1) {
    18             perror("mkfifo");
    19             exit(0);
    20         }
    21     }
    22 
    23     ret = access("fifo2", F_OK);
    24     if(ret == -1) {
    25         // 文件不存在
    26         printf("管道不存在,创建对应的有名管道\n");
    27         ret = mkfifo("fifo2", 0664);
    28         if(ret == -1) {
    29             perror("mkfifo");
    30             exit(0);
    31         }
    32     }
    33 
    34     // 2.以只读的方式打开管道fifo1
    35     int fdr = open("fifo1", O_RDONLY);
    36     if(fdr == -1) {
    37         perror("open");
    38         exit(0);
    39     }
    40     printf("打开管道fifo1成功,等待读取...\n");
    41     // 3.以只写的方式打开管道fifo2
    42     int fdw = open("fifo2", O_WRONLY);
    43     if(fdw == -1) {
    44         perror("open");
    45         exit(0);
    46     }
    47     printf("打开管道fifo2成功,等待写入...\n");
    48 
    49     char buf[128];
    50 
    51     // 4.循环的读写数据
    52     while(1) {
    53         // 5.读管道数据
    54         memset(buf, 0, 128);
    55         ret = read(fdr, buf, 128);
    56         if(ret <= 0) {
    57             perror("read");
    58             break;
    59         }
    60         printf("buf: %s\n", buf);
    61 
    62         memset(buf, 0, 128);
    63         // 获取标准输入的数据
    64         fgets(buf, 128, stdin);
    65         // 写数据
    66         ret = write(fdw, buf, strlen(buf));
    67         if(ret == -1) {
    68             perror("write");
    69             exit(0);
    70         }
    71     }
    72 
    73     // 6.关闭文件描述符
    74     close(fdr);
    75     close(fdw);
    76 
    77     return 0;
    78 }

    内存映射

     

     

     

    代码

     1 /*
     2     #include <sys/mman.h>
     3     void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
     4         - 功能:将一个文件或者设备的数据映射到内存中
     5         - 参数:
     6             - void *addr: NULL, 由内核指定
     7             - length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
     8                     获取文件的长度:stat lseek
     9             - prot : 对申请的内存映射区的操作权限
    10                 -PROT_EXEC :可执行的权限
    11                 -PROT_READ :读权限
    12                 -PROT_WRITE :写权限
    13                 -PROT_NONE :没有权限
    14                 要操作映射内存,必须要有读的权限。
    15                 PROT_READ、PROT_READ|PROT_WRITE
    16             - flags :
    17                 - MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项
    18                 - MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
    19             - fd: 需要映射的那个文件的文件描述符
    20                 - 通过open得到,open的是一个磁盘文件
    21                 - 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
    22                     prot: PROT_READ                open:只读/读写 
    23                     prot: PROT_READ | PROT_WRITE   open:读写
    24             - offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不便宜。
    25         - 返回值:返回创建的内存的首地址
    26             失败返回MAP_FAILED,(void *) -1
    27 
    28     int munmap(void *addr, size_t length);
    29         - 功能:释放内存映射
    30         - 参数:
    31             - addr : 要释放的内存的首地址
    32             - length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
    33 */
    34 
    35 /*
    36     使用内存映射实现进程间通信:
    37     1.有关系的进程(父子进程)
    38         - 还没有子进程的时候
    39             - 通过唯一的父进程,先创建内存映射区
    40         - 有了内存映射区以后,创建子进程
    41         - 父子进程共享创建的内存映射区
    42     
    43     2.没有关系的进程间通信
    44         - 准备一个大小不是0的磁盘文件
    45         - 进程1 通过磁盘文件创建内存映射区
    46             - 得到一个操作这块内存的指针
    47         - 进程2 通过磁盘文件创建内存映射区
    48             - 得到一个操作这块内存的指针
    49         - 使用内存映射区通信
    50 
    51     注意:内存映射区通信,是非阻塞。
    52 */
    53 
    54 #include <stdio.h>
    55 #include <sys/mman.h>
    56 #include <fcntl.h>
    57 #include <sys/types.h>
    58 #include <unistd.h>
    59 #include <string.h>
    60 #include <stdlib.h>
    61 #include <wait.h>
    62 
    63 // 作业:使用内存映射实现没有关系的进程间的通信。
    64 int main() {
    65 
    66     // 1.打开一个文件
    67     int fd = open("test.txt", O_RDWR);
    68     int size = lseek(fd, 0, SEEK_END);  // 获取文件的大小
    69 
    70     // 2.创建内存映射区
    71     void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    72     if(ptr == MAP_FAILED) {
    73         perror("mmap");
    74         exit(0);
    75     }
    76 
    77     // 3.创建子进程
    78     pid_t pid = fork();
    79     if(pid > 0) {
    80         wait(NULL);
    81         // 父进程
    82         char buf[64];
    83         strcpy(buf, (char *)ptr);
    84         printf("read data : %s\n", buf);
    85        
    86     }else if(pid == 0){
    87         // 子进程
    88         strcpy((char *)ptr, "nihao a, son!!!");
    89     }
    90 
    91     // 关闭内存映射区
    92     munmap(ptr, size);
    93 
    94     return 0;
    95 }

     思考问题

     内存映射的注意事项

    1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?

    void * ptr = mmap(...);
    ptr++; 可以对其进行++操作
    munmap(ptr, len); // 错误,要保存地址

    2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?

    错误,返回MAP_FAILED
    open()函数中的权限建议和prot参数的权限保持一致。

    3.如果文件偏移量为1000会怎样?

    偏移量必须是4K的整数倍,返回MAP_FAILED

    4.mmap什么情况下会调用失败?

    - 第二个参数:length = 0
    - 第三个参数:prot
    - 只指定了写权限
    - prot PROT_READ | PROT_WRITE
    第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY

    5.可以open的时候O_CREAT一个新文件来创建映射区吗?

    - 可以的,但是创建的文件的大小如果为0的话,肯定不行
    - 可以对新的文件进行扩展
    - lseek()
    - truncate()

    6.mmap后关闭文件描述符,对mmap映射有没有影响?

    int fd = open("XXX");
    mmap(,,,,fd,0);
    close(fd);
    映射区还存在,创建映射区的fd被关闭,没有任何影响。

    7.对ptr越界操作会怎样?

    void * ptr = mmap(NULL, 100,,,,,);
    4K
    越界操作操作的是非法的内存 -> 段错误

    内存映射可以实现:1.进程通信;2.文件拷贝(但是不能拷贝太大的文件,一般也不用于文件拷贝)

    内存映射的匿名映射:不需要文件实体进程一个内存映射

    代码

    copy.c

     1 // 使用内存映射实现文件拷贝的功能
     2 /*
     3     思路:
     4         1.对原始的文件进行内存映射
     5         2.创建一个新文件(拓展该文件)
     6         3.把新文件的数据映射到内存中
     7         4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
     8         5.释放资源
     9 */
    10 #include <stdio.h>
    11 #include <sys/mman.h>
    12 #include <sys/types.h>
    13 #include <sys/stat.h>
    14 #include <fcntl.h>
    15 #include <unistd.h>
    16 #include <string.h>
    17 #include <stdlib.h>
    18 
    19 int main() {
    20 
    21     // 1.对原始的文件进行内存映射
    22     int fd = open("english.txt", O_RDWR);
    23     if(fd == -1) {
    24         perror("open");
    25         exit(0);
    26     }
    27 
    28     // 获取原始文件的大小
    29     int len = lseek(fd, 0, SEEK_END);
    30 
    31     // 2.创建一个新文件(拓展该文件)
    32     int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
    33     if(fd1 == -1) {
    34         perror("open");
    35         exit(0);
    36     }
    37     
    38     // 对新创建的文件进行拓展
    39     truncate("cpy.txt", len);
    40     write(fd1, " ", 1);
    41 
    42     // 3.分别做内存映射
    43     void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    44     void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);
    45 
    46     if(ptr == MAP_FAILED) {
    47         perror("mmap");
    48         exit(0);
    49     }
    50 
    51     if(ptr1 == MAP_FAILED) {
    52         perror("mmap");
    53         exit(0);
    54     }
    55 
    56     // 内存拷贝
    57     memcpy(ptr1, ptr, len);
    58     
    59     // 释放资源
    60     munmap(ptr1, len);
    61     munmap(ptr, len);
    62 
    63     close(fd1);
    64     close(fd);
    65 
    66     return 0;
    67 }

    mmap-anon.c

     1 /*
     2     匿名映射:不需要文件实体进程一个内存映射
     3 */
     4 
     5 #include <stdio.h>
     6 #include <sys/mman.h>
     7 #include <sys/types.h>
     8 #include <sys/stat.h>
     9 #include <fcntl.h>
    10 #include <unistd.h>
    11 #include <string.h>
    12 #include <stdlib.h>
    13 #include <sys/wait.h>
    14 
    15 int main() {
    16 
    17     // 1.创建匿名内存映射区
    18     int len = 4096;
    19     void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    20     if(ptr == MAP_FAILED) {
    21         perror("mmap");
    22         exit(0);
    23     }
    24 
    25     // 父子进程间通信
    26     pid_t pid = fork();
    27 
    28     if(pid > 0) {
    29         // 父进程
    30         strcpy((char *) ptr, "hello, world");
    31         wait(NULL);
    32     }else if(pid == 0) {
    33         // 子进程
    34         sleep(1);
    35         printf("%s\n", (char *)ptr);
    36     }
    37 
    38     // 释放内存映射区
    39     int ret = munmap(ptr, len);
    40 
    41     if(ret == -1) {
    42         perror("munmap");
    43         exit(0);
    44     }
    45     return 0;
    46 }

    信号概述

    信号

     

    LUNIX信号一览表

    红色信号 面试必须记住

     

     

    信号的5种默认处理动作

    kill、raise、abort函数

    代码

     1 /*  
     2     #include <sys/types.h>
     3     #include <signal.h>
     4 
     5     int kill(pid_t pid, int sig);
     6         - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig
     7         - 参数:
     8             - pid :
     9                 > 0 : 将信号发送给指定的进程
    10                 = 0 : 将信号发送给当前的进程组
    11                 = -1 : 将信号发送给每一个有权限接收这个信号的进程
    12                 < -1 : 这个pid=某个进程组的ID取反 (-12345)
    13             - sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号
    14 
    15         kill(getppid(), 9);
    16         kill(getpid(), 9);
    17         
    18     int raise(int sig);
    19         - 功能:给当前进程发送信号
    20         - 参数:
    21             - sig : 要发送的信号
    22         - 返回值:
    23             - 成功 0
    24             - 失败 非0
    25         kill(getpid(), sig);   
    26 
    27     void abort(void);
    28         - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程
    29         kill(getpid(), SIGABRT);
    30 */
    31 
    32 #include <stdio.h>
    33 #include <sys/types.h>
    34 #include <signal.h>
    35 #include <unistd.h>
    36 
    37 int main() {
    38 
    39     pid_t pid = fork();
    40 
    41     if(pid == 0) {
    42         // 子进程
    43         int i = 0;
    44         for(i = 0; i < 5; i++) {
    45             printf("child process\n");
    46             sleep(1);
    47         }
    48 
    49     } else if(pid > 0) {
    50         // 父进程
    51         printf("parent process\n");
    52         sleep(2);
    53         printf("kill child process now\n");
    54         kill(pid, SIGINT);
    55     }
    56 
    57     return 0;
    58 }

    alarm函数

    信号相关的函数

    代码

    alarm.c

     1 /*
     2     #include <unistd.h>
     3     unsigned int alarm(unsigned int seconds);
     4         - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
     5                 函数会给当前的进程发送一个信号:SIGALARM
     6         - 参数:
     7             seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
     8                     取消一个定时器,通过alarm(0)。
     9         - 返回值:
    10             - 之前没有定时器,返回0
    11             - 之前有定时器,返回之前的定时器剩余的时间
    12 
    13     - SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
    14         alarm(10);  -> 返回0
    15         过了1秒
    16         alarm(5);   -> 返回9
    17 
    18     alarm(100) -> 该函数是不阻塞的
    19 */
    20 
    21 #include <stdio.h>
    22 #include <unistd.h>
    23 
    24 int main() {
    25 
    26     int seconds = alarm(5);
    27     printf("seconds = %d\n", seconds);  // 0
    28 
    29     sleep(2);
    30     seconds = alarm(2);    // 不阻塞
    31     printf("seconds = %d\n", seconds);  // 3
    32 
    33     while(1) {
    34     }
    35 
    36     return 0;
    37 }

    alarm1.c

     1 // 1秒钟电脑能数多少个数?
     2 #include <stdio.h>
     3 #include <unistd.h>
     4 
     5 /*
     6     实际的时间 = 内核时间 + 用户时间 + 消耗的时间
     7     进行文件IO操作的时候比较浪费时间
     8 
     9     定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会计时。
    10 */
    11 
    12 int main() {    
    13 
    14     alarm(1);
    15 
    16     int i = 0;
    17     while(1) {
    18         printf("%i\n", i++);
    19     }
    20 
    21     return 0;
    22 }

    setitimer定时器函数

    setitimer函数

     

     

    代码

     1 /*
     2     #include <sys/time.h>
     3     int setitimer(int which, const struct itimerval *new_value,
     4                         struct itimerval *old_value);
     5     
     6         - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时
     7         - 参数:
     8             - which : 定时器以什么时间计时
     9               ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用
    10               ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM
    11               ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF
    12 
    13             - new_value: 设置定时器的属性
    14             
    15                 struct itimerval {      // 定时器的结构体
    16                 struct timeval it_interval;  // 每个阶段的时间,间隔时间
    17                 struct timeval it_value;     // 延迟多长时间执行定时器
    18                 };
    19 
    20                 struct timeval {        // 时间的结构体
    21                     time_t      tv_sec;     //  秒数     
    22                     suseconds_t tv_usec;    //  微秒    
    23                 };
    24 
    25             过10秒后,每个2秒定时一次
    26            
    27             - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
    28         
    29         - 返回值:
    30             成功 0
    31             失败 -1 并设置错误号
    32 */
    33 
    34 #include <sys/time.h>
    35 #include <stdio.h>
    36 #include <stdlib.h>
    37 
    38 // 过3秒以后,每隔2秒钟定时一次
    39 int main() {
    40 
    41     struct itimerval new_value;
    42 
    43     // 设置间隔的时间
    44     new_value.it_interval.tv_sec = 2;
    45     new_value.it_interval.tv_usec = 0;
    46 
    47     // 设置延迟的时间,3秒之后开始第一次定时
    48     new_value.it_value.tv_sec = 3;
    49     new_value.it_value.tv_usec = 0;
    50 
    51 
    52     int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    53     printf("定时器开始了...\n");
    54 
    55     if(ret == -1) {
    56         perror("setitimer");
    57         exit(0);
    58     }
    59 
    60     getchar();
    61 
    62     return 0;
    63 }

    signal信号捕捉函数

     

    代码

    signal.c

     1 /*
     2     #include <signal.h>
     3     typedef void (*sighandler_t)(int);
     4     sighandler_t signal(int signum, sighandler_t handler);
     5         - 功能:设置某个信号的捕捉行为
     6         - 参数:
     7             - signum: 要捕捉的信号
     8             - handler: 捕捉到信号要如何处理
     9                 - SIG_IGN : 忽略信号
    10                 - SIG_DFL : 使用信号默认的行为
    11                 - 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
    12                 回调函数:
    13                     - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
    14                     - 不是程序员调用,而是当信号产生,由内核调用
    15                     - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
    16 
    17         - 返回值:
    18             成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
    19             失败,返回SIG_ERR,设置错误号
    20             
    21     SIGKILL SIGSTOP不能被捕捉,不能被忽略。
    22 */
    23 
    24 #include <sys/time.h>
    25 #include <stdio.h>
    26 #include <stdlib.h>
    27 #include <signal.h>
    28 
    29 void myalarm(int num) {
    30     printf("捕捉到了信号的编号是:%d\n", num);
    31     printf("xxxxxxx\n");
    32 }
    33 
    34 // 过3秒以后,每隔2秒钟定时一次
    35 int main() {
    36 
    37     // 注册信号捕捉
    38     // signal(SIGALRM, SIG_IGN);
    39     // signal(SIGALRM, SIG_DFL);
    40     // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。
    41     signal(SIGALRM, myalarm);
    42 
    43     struct itimerval new_value;
    44 
    45     // 设置间隔的时间
    46     new_value.it_interval.tv_sec = 2;
    47     new_value.it_interval.tv_usec = 0;
    48 
    49     // 设置延迟的时间,3秒之后开始第一次定时
    50     new_value.it_value.tv_sec = 3;
    51     new_value.it_value.tv_usec = 0;
    52 
    53     int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    54     printf("定时器开始了...\n");
    55 
    56     if(ret == -1) {
    57         perror("setitimer");
    58         exit(0);
    59     }
    60 
    61     getchar();
    62 
    63     return 0;
    64 }

    信号集及相关函数

    信号集

    阻塞信号集和未决信号集

    1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)

    2.信号产生但是没有被处理 (未决)
    - 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
    - SIGINT信号状态被存储在第二个标志位上
    - 这个标志位的值为0, 说明信号不是未决状态
    - 这个标志位的值为1, 说明信号处于未决状态

    3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
    - 阻塞信号集默认不阻塞任何的信号
    - 如果想要阻塞某些信号需要用户调用系统的API

    4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
    - 如果没有阻塞,这个信号就被处理
    - 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

    代码

     1 /*
     2     以下信号集相关的函数都是对自定义的信号集进行操作。
     3 
     4     int sigemptyset(sigset_t *set);
     5         - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
     6         - 参数:set,传出参数,需要操作的信号集
     7         - 返回值:成功返回0, 失败返回-1
     8 
     9     int sigfillset(sigset_t *set);
    10         - 功能:将信号集中的所有的标志位置为1
    11         - 参数:set,传出参数,需要操作的信号集
    12         - 返回值:成功返回0, 失败返回-1
    13 
    14     int sigaddset(sigset_t *set, int signum);
    15         - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
    16         - 参数:
    17             - set:传出参数,需要操作的信号集
    18             - signum:需要设置阻塞的那个信号
    19         - 返回值:成功返回0, 失败返回-1
    20 
    21     int sigdelset(sigset_t *set, int signum);
    22         - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
    23         - 参数:
    24             - set:传出参数,需要操作的信号集
    25             - signum:需要设置不阻塞的那个信号
    26         - 返回值:成功返回0, 失败返回-1
    27 
    28     int sigismember(const sigset_t *set, int signum);
    29         - 功能:判断某个信号是否阻塞
    30         - 参数:
    31             - set:需要操作的信号集
    32             - signum:需要判断的那个信号
    33         - 返回值:
    34             1 : signum被阻塞
    35             0 : signum不阻塞
    36             -1 : 失败
    37 
    38 */
    39 
    40 #include <signal.h>
    41 #include <stdio.h>
    42 
    43 int main() {
    44 
    45     // 创建一个信号集
    46     sigset_t set;
    47 
    48     // 清空信号集的内容
    49     sigemptyset(&set);
    50 
    51     // 判断 SIGINT 是否在信号集 set 里
    52     int ret = sigismember(&set, SIGINT);
    53     if(ret == 0) {
    54         printf("SIGINT 不阻塞\n");
    55     } else if(ret == 1) {
    56         printf("SIGINT 阻塞\n");
    57     }
    58 
    59     // 添加几个信号到信号集中
    60     sigaddset(&set, SIGINT);
    61     sigaddset(&set, SIGQUIT);
    62 
    63     // 判断SIGINT是否在信号集中
    64     ret = sigismember(&set, SIGINT);
    65     if(ret == 0) {
    66         printf("SIGINT 不阻塞\n");
    67     } else if(ret == 1) {
    68         printf("SIGINT 阻塞\n");
    69     }
    70 
    71     // 判断SIGQUIT是否在信号集中
    72     ret = sigismember(&set, SIGQUIT);
    73     if(ret == 0) {
    74         printf("SIGQUIT 不阻塞\n");
    75     } else if(ret == 1) {
    76         printf("SIGQUIT 阻塞\n");
    77     }
    78 
    79     // 从信号集中删除一个信号
    80     sigdelset(&set, SIGQUIT);
    81 
    82     // 判断SIGQUIT是否在信号集中
    83     ret = sigismember(&set, SIGQUIT);
    84     if(ret == 0) {
    85         printf("SIGQUIT 不阻塞\n");
    86     } else if(ret == 1) {
    87         printf("SIGQUIT 阻塞\n");
    88     }
    89 
    90     return 0;
    91 }

    信号集相关的函数

    sigprocmask函数使用

     

    代码

     1 /*
     2     int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
     3         - 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
     4         - 参数:
     5             - how : 如何对内核阻塞信号集进行处理
     6                 SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变
     7                     假设内核中默认的阻塞信号集是mask, mask | set
     8                 SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
     9                     mask &= ~set
    10                 SIG_SETMASK:覆盖内核中原来的值
    11             
    12             - set :已经初始化好的用户自定义的信号集
    13             - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
    14         - 返回值:
    15             成功:0
    16             失败:-1
    17                 设置错误号:EFAULT、EINVAL
    18 
    19     int sigpending(sigset_t *set);
    20         - 功能:获取内核中的未决信号集
    21         - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
    22 */
    23 
    24 // 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
    25 // 设置某些信号是阻塞的,通过键盘产生这些信号
    26 
    27 #include <stdio.h>
    28 #include <signal.h>
    29 #include <stdlib.h>
    30 #include <unistd.h>
    31 
    32 int main() {
    33 
    34     // 设置2、3号信号阻塞
    35     sigset_t set;
    36     sigemptyset(&set);
    37     // 将2号和3号信号添加到信号集中
    38     sigaddset(&set, SIGINT);
    39     sigaddset(&set, SIGQUIT);
    40 
    41     // 修改内核中的阻塞信号集
    42     sigprocmask(SIG_BLOCK, &set, NULL);
    43 
    44     int num = 0;
    45 
    46     while(1) {
    47         num++;
    48         // 获取当前的未决信号集的数据
    49         sigset_t pendingset;
    50         sigemptyset(&pendingset);
    51         sigpending(&pendingset);
    52 
    53         // 遍历前32位
    54         for(int i = 1; i <= 31; i++) {
    55             if(sigismember(&pendingset, i) == 1) {
    56                 printf("1");
    57             }else if(sigismember(&pendingset, i) == 0) {
    58                 printf("0");
    59             }else {
    60                 perror("sigismember");
    61                 exit(0);
    62             }
    63         }
    64 
    65         printf("\n");
    66         sleep(1);
    67         if(num == 10) {
    68             // 解除阻塞
    69             sigprocmask(SIG_UNBLOCK, &set, NULL);
    70         }
    71 
    72     }
    73 
    74 
    75     return 0;
    76 }

    sigaction信号捕捉函数

    代码

     1 /*
     2     #include <signal.h>
     3     int sigaction(int signum, const struct sigaction *act,
     4                             struct sigaction *oldact);
     5 
     6         - 功能:检查或者改变信号的处理。信号捕捉
     7         - 参数:
     8             - signum : 需要捕捉的信号的编号或者宏值(信号的名称)
     9             - act :捕捉到信号之后的处理动作
    10             - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
    11         - 返回值:
    12             成功 0
    13             失败 -1
    14 
    15      struct sigaction {
    16         // 函数指针,指向的函数就是信号捕捉到之后的处理函数
    17         void     (*sa_handler)(int);
    18         // 不常用
    19         void     (*sa_sigaction)(int, siginfo_t *, void *);
    20         // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
    21         sigset_t   sa_mask;
    22         // 使用哪一个信号处理对捕捉到的信号进行处理
    23         // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
    24         int        sa_flags;
    25         // 被废弃掉了
    26         void     (*sa_restorer)(void);
    27     };
    28 
    29 */
    30 #include <sys/time.h>
    31 #include <stdio.h>
    32 #include <stdlib.h>
    33 #include <signal.h>
    34 
    35 void myalarm(int num) {
    36     printf("捕捉到了信号的编号是:%d\n", num);
    37     printf("xxxxxxx\n");
    38 }
    39 
    40 // 过3秒以后,每隔2秒钟定时一次
    41 int main() {
    42 
    43     struct sigaction act;
    44     act.sa_flags = 0;
    45     act.sa_handler = myalarm;
    46     sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集
    47    
    48     // 注册信号捕捉
    49     sigaction(SIGALRM, &act, NULL);
    50 
    51     struct itimerval new_value;
    52 
    53     // 设置间隔的时间
    54     new_value.it_interval.tv_sec = 2;
    55     new_value.it_interval.tv_usec = 0;
    56 
    57     // 设置延迟的时间,3秒之后开始第一次定时
    58     new_value.it_value.tv_sec = 3;
    59     new_value.it_value.tv_usec = 0;
    60 
    61     int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    62     printf("定时器开始了...\n");
    63 
    64     if(ret == -1) {
    65         perror("setitimer");
    66         exit(0);
    67     }
    68 
    69     // getchar();
    70     while(1);
    71 
    72     return 0;
    73 }

    SIGCHLG信号

     1 /*
     2     SIGCHLD信号产生的3个条件:
     3         1.子进程结束
     4         2.子进程暂停了
     5         3.子进程继续运行
     6         都会给父进程发送该信号,父进程默认忽略该信号。
     7     
     8     使用SIGCHLD信号解决僵尸进程的问题。
     9 */
    10 
    11 #include <stdio.h>
    12 #include <unistd.h>
    13 #include <sys/types.h>
    14 #include <sys/stat.h>
    15 #include <signal.h>
    16 #include <sys/wait.h>
    17 
    18 void myFun(int num) {
    19     printf("捕捉到的信号 :%d\n", num);
    20     // 回收子进程PCB的资源
    21     // while(1) {
    22     //     wait(NULL); 
    23     // }
    24     while(1) {
    25        int ret = waitpid(-1, NULL, WNOHANG);
    26        if(ret > 0) {
    27            printf("child die , pid = %d\n", ret);
    28        } else if(ret == 0) {
    29            // 说明还有子进程或者
    30            break;
    31        } else if(ret == -1) {
    32            // 没有子进程
    33            break;
    34        }
    35     }
    36 }
    37 
    38 int main() {
    39 
    40     // 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
    41     sigset_t set;
    42     sigemptyset(&set);
    43     sigaddset(&set, SIGCHLD);
    44     sigprocmask(SIG_BLOCK, &set, NULL);
    45 
    46     // 创建一些子进程
    47     pid_t pid;
    48     for(int i = 0; i < 20; i++) {
    49         pid = fork();
    50         if(pid == 0) {
    51             break;
    52         }
    53     }
    54 
    55     if(pid > 0) {
    56         // 父进程
    57 
    58         // 捕捉子进程死亡时发送的SIGCHLD信号
    59         struct sigaction act;
    60         act.sa_flags = 0;
    61         act.sa_handler = myFun;
    62         sigemptyset(&act.sa_mask);
    63         sigaction(SIGCHLD, &act, NULL);
    64 
    65         // 注册完信号捕捉以后,解除阻塞
    66         sigprocmask(SIG_UNBLOCK, &set, NULL);
    67 
    68         while(1) {
    69             printf("parent process pid : %d\n", getpid());
    70             sleep(2);
    71         }
    72     } else if( pid == 0) {
    73         // 子进程
    74         printf("child process pid : %d\n", getpid());
    75     }
    76 
    77     return 0;
    78 }

    共享内存

     1 共享内存相关的函数
     2 #include <sys/ipc.h>
     3 #include <sys/shm.h>
     4 
     5 int shmget(key_t key, size_t size, int shmflg);
     6     - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
     7         新创建的内存段中的数据都会被初始化为0
     8     - 参数:
     9         - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
    10                 一般使用16进制表示,非0值
    11         - size: 共享内存的大小
    12         - shmflg: 属性
    13             - 访问权限
    14             - 附加属性:创建/判断共享内存是不是存在
    15                 - 创建:IPC_CREAT
    16                 - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
    17                     IPC_CREAT | IPC_EXCL | 0664
    18         - 返回值:
    19             失败:-1 并设置错误号
    20             成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。
    21 
    22 
    23 void *shmat(int shmid, const void *shmaddr, int shmflg);
    24     - 功能:和当前的进程进行关联
    25     - 参数:
    26         - shmid : 共享内存的标识(ID),由shmget返回值获取
    27         - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
    28         - shmflg : 对共享内存的操作
    29             - 读 : SHM_RDONLY, 必须要有读权限
    30             - 读写: 0
    31     - 返回值:
    32         成功:返回共享内存的首(起始)地址。  失败(void *) -1
    33 
    34 
    35 int shmdt(const void *shmaddr);
    36     - 功能:解除当前进程和共享内存的关联
    37     - 参数:
    38         shmaddr:共享内存的首地址
    39     - 返回值:成功 0, 失败 -1
    40 
    41 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    42     - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。
    43     - 参数:
    44         - shmid: 共享内存的ID
    45         - cmd : 要做的操作
    46             - IPC_STAT : 获取共享内存的当前的状态
    47             - IPC_SET : 设置共享内存的状态
    48             - IPC_RMID: 标记共享内存被销毁
    49         - buf:需要设置或者获取的共享内存的属性信息
    50             - IPC_STAT : buf存储数据
    51             - IPC_SET : buf中需要初始化数据,设置到内核中
    52             - IPC_RMID : 没有用,NULL
    53 
    54 key_t ftok(const char *pathname, int proj_id);
    55     - 功能:根据指定的路径名,和int值,生成一个共享内存的key
    56     - 参数:
    57         - pathname:指定一个存在的路径
    58             /home/nowcoder/Linux/a.txt
    59             / 
    60         - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
    61                    范围 : 0-255  一般指定一个字符 'a'
    62 
    63 
    64 问题1:操作系统如何知道一块共享内存被多少个进程关联?
    65     - 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
    66     - shm_nattach 记录了关联的进程个数
    67 
    68 问题2:可不可以对共享内存进行多次删除 shmctl
    69     - 可以的
    70     - 因为shmctl 标记删除共享内存,不是直接删除
    71     - 什么时候真正删除呢?
    72         当和共享内存关联的进程数为0的时候,就真正被删除
    73     - 当共享内存的key为0的时候,表示共享内存被标记删除了
    74         如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。
    75 
    76     共享内存和内存映射的区别
    77     1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
    78     2.共享内存效果更高
    79     3.内存
    80         所有的进程操作的是同一块共享内存。
    81         内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。
    82     4.数据安全
    83         - 进程突然退出
    84             共享内存还存在
    85             内存映射区消失
    86         - 运行进程的电脑死机,宕机了
    87             数据存在在共享内存中,没有了
    88             内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。
    89 
    90     5.生命周期
    91         - 内存映射区:进程退出,内存映射区销毁
    92         - 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机
    93             如果一个进程退出,会自动和共享内存进行取消关联。

    代码

    write_shm.c

     1 #include <stdio.h>
     2 #include <sys/ipc.h>
     3 #include <sys/shm.h>
     4 #include <string.h>
     5 
     6 int main() {    
     7 
     8     // 1.创建一个共享内存
     9     int shmid = shmget(100, 4096, IPC_CREAT|0664);
    10     printf("shmid : %d\n", shmid);
    11     
    12     // 2.和当前进程进行关联
    13     void * ptr = shmat(shmid, NULL, 0);
    14 
    15     char * str = "helloworld";
    16 
    17     // 3.写数据
    18     memcpy(ptr, str, strlen(str) + 1);
    19 
    20     printf("按任意键继续\n");
    21     getchar();
    22 
    23     // 4.解除关联
    24     shmdt(ptr);
    25 
    26     // 5.删除共享内存
    27     shmctl(shmid, IPC_RMID, NULL);
    28 
    29     return 0;
    30 }

    read_shm.c

     1 #include <stdio.h>
     2 #include <sys/ipc.h>
     3 #include <sys/shm.h>
     4 #include <string.h>
     5 
     6 int main() {    
     7 
     8     // 1.获取一个共享内存
     9     int shmid = shmget(100, 0, IPC_CREAT);
    10     printf("shmid : %d\n", shmid);
    11 
    12     // 2.和当前进程进行关联
    13     void * ptr = shmat(shmid, NULL, 0);
    14 
    15     // 3.读数据
    16     printf("%s\n", (char *)ptr);
    17     
    18     printf("按任意键继续\n");
    19     getchar();
    20 
    21     // 4.解除关联
    22     shmdt(ptr);
    23 
    24     // 5.删除共享内存
    25     shmctl(shmid, IPC_RMID, NULL);
    26 
    27     return 0;
    28 }

    守护进程

    终端

     进程组

     会话

     

     进程组、会话操作函数

    守护进程

     守护进程的创建步骤

    代码

     1 /*
     2     写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
     3 */
     4 
     5 #include <stdio.h>
     6 #include <sys/stat.h>
     7 #include <sys/types.h>
     8 #include <unistd.h>
     9 #include <fcntl.h>
    10 #include <sys/time.h>
    11 #include <signal.h>
    12 #include <time.h>
    13 #include <stdlib.h>
    14 #include <string.h>
    15 
    16 void work(int num) {
    17     // 捕捉到信号之后,获取系统时间,写入磁盘文件
    18     time_t tm = time(NULL);
    19     struct tm * loc = localtime(&tm);
    20     // char buf[1024];
    21 
    22     // sprintf(buf, "%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon
    23     // ,loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);
    24 
    25     // printf("%s\n", buf);
    26 
    27     char * str = asctime(loc);
    28     int fd = open("time.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
    29     write(fd ,str, strlen(str));
    30     close(fd);
    31 }
    32 
    33 int main() {
    34 
    35     // 1.创建子进程,退出父进程
    36     pid_t pid = fork();
    37 
    38     if(pid > 0) {
    39         exit(0);
    40     }
    41 
    42     // 2.将子进程重新创建一个会话
    43     setsid();
    44 
    45     // 3.设置掩码
    46     umask(022);
    47 
    48     // 4.更改工作目录
    49     chdir("/home/nowcoder/");
    50 
    51     // 5. 关闭、重定向文件描述符
    52     int fd = open("/dev/null", O_RDWR);
    53     dup2(fd, STDIN_FILENO);
    54     dup2(fd, STDOUT_FILENO);
    55     dup2(fd, STDERR_FILENO);
    56 
    57     // 6.业务逻辑
    58 
    59     // 捕捉定时信号
    60     struct sigaction act;
    61     act.sa_flags = 0;
    62     act.sa_handler = work;
    63     sigemptyset(&act.sa_mask);
    64     sigaction(SIGALRM, &act, NULL);
    65 
    66     struct itimerval val;
    67     val.it_value.tv_sec = 2;
    68     val.it_value.tv_usec = 0;
    69     val.it_interval.tv_sec = 2;
    70     val.it_interval.tv_usec = 0;
    71 
    72     // 创建定时器
    73     setitimer(ITIMER_REAL, &val, NULL);
    74 
    75     // 不让进程结束
    76     while(1) {
    77         sleep(10);
    78     }
    79 
    80     return 0;
    81 }
  • 相关阅读:
    高性能css动画
    关于thinkphp验证码的那些事
    DOM对象的属性
    关于data属性的一些常见的处理方式
    webstorm快捷键整理
    javascript模块化编程
    2016年5月30日上午(传智Bootstrap笔记六(图片样式))
    Bootstrap列排序
    2016年5月29日晚上(传智Bootstrap笔记五(表单2))
    2016年5月29日晚上(传智Bootstrap笔记四(栅格系统 ))
  • 原文地址:https://www.cnblogs.com/weixq351/p/15929389.html
Copyright © 2020-2023  润新知