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 }

 

posted @ 2022-02-23 22:02  白雪儿  Views(108)  Comments(0Edit  收藏  举报