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 }