当前位置:   article > 正文

2.Linux多进程_linux 多进程学习

linux 多进程学习

1 进程的概述

1.1 程序和进程

程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程:

◼ 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解 释文件中的其他信息。(ELF可执行连接格式)

机器语言指令:对程序算法进行编码。

◼ 程序入口地址:标识程序开始执行时的起始指令位置。

◼ 数据:程序文件包含的变量初始值和程序使用的字面量值(比如字符串)。

◼ 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多重用途,其中包括调试 和运行时的符号解析(动态链接)。

◼ 共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以 及加载共享库的动态连接器的路径名。

◼ 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。

◼ 进程是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的 一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是 基本的分配单元,也是基本的执行单元。

◼ 可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用 以执行程序的各项系统资源。从内核的角度看,进程由用户内存空间和一系列内核数 据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结 构则用于维护进程状态信息。记录在内核数据结构中的信息包括许多与进程相关的标 识号(IDs)、虚拟内存表、打开文件的描述符表、信号传递及处理的有关信息、进 程资源使用及限制、当前工作目录和大量的其他信息。

1.2 单道、多道程序设计

 1.3 时间片

 1.4 并行和并发

 1.5 进程控制块(PCB)

2 进程状态转换

2.1 进程的状态

 2.2 进程相关命令

 2.3 进程号和相关函数

3 进程创建

3.1 进程的创建

 【fork函数】

  1. /*
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. pid_t fork(void);
  5.     函数的作用:用于创建子进程
  6.     返回值:
  7.         fork()的返回值会返回两次;一次是在父进程中,一次是在子进程中;
  8.         在父进程中返回创建的子进程ID,
  9.         在子进程中返回0
  10.         如何区分父进程和子进程:通过fork的返回值。
  11.         在父进程中返回-1,表示创建子进程失败,并设置errno
  12. */
  13. #include <sys/types.h>
  14. #include <unistd.h>
  15. #include <stdio.h>
  16. int main()
  17. {
  18.     //创建子进程
  19.     pid_t pid=fork();
  20.     //判断是父进程还是子进程
  21.     if(pid>0)
  22.     {
  23.         printf("pid : %d\n",pid);
  24.         //如果大于0,返回的是创建的子进程的进程号,当前是父进程
  25.         printf("I am parent process,pid : %d, ppid : %d",getpid(),getppid());    
  26.     }
  27.     else if(pid==0)
  28.     {
  29.         //当前是子进程
  30.         printf("I am child process,pid : %d, ppid : %d\n",getpid(),getppid());
  31.     }
  32.     //for循环
  33.     for (int i = 0; i < 3; i++)
  34.     {
  35.         printf("i : %d, pid : %d\n",i , getpid());
  36.         sleep(1);
  37.     }
  38.      return 0;
  39. }
  40. /*
  41. 实际上,更准确来说,Linux的fork()使用是通过写时拷贝(copy-on-write)实现
  42. 写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
  43. 内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
  44. 只用在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。
  45. 也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
  46. 注意:fork之后父子进程共享文件,fork产生的子进程与父进程相同的文件描述符指向相同
  47. 的文件表,引用计数增加。
  48. */

 4 父进程虚拟地址空间情况

 GDB 多进程调试

 5 exec函数族

5.1 exec 函数族介绍

 5.2 exec 函数族作用图解

 5.3 exec 函数族

 【execl函数】

  1. /*
  2. #include <unistd.h>
  3. int execl(const char *pathname, const char *arg, ...);
  4.     -参数:
  5.         -path:需要指定的执行文件的路径或者名称
  6.           a.out  /home/xiong/a.out推荐使用绝对路径
  7.         -arg:是执行可执行文件所需要的参数列表
  8.           第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
  9.           从第二个参数开始往后,就是程序执行所需要的参数列表。
  10.           参数最后需要以NULL结束(哨兵)
  11.     返回值:
  12.         只有当调用失败,才会有返回值,返回-1,并且设置errno
  13.         如果调用成功,没有返回值。
  14. */
  15. #include <unistd.h>
  16. #include <stdio.h>
  17. int main()
  18. {
  19.     //创建一个子进程,在子进程中执行exec函数族中的函数
  20.     pid_t pid=fork();
  21.     if(pid>0)
  22.     {
  23.         //父进程
  24.         printf("I am parent process, pid : %d\n",getpid());
  25.         sleep(1);
  26.     }
  27.     else if(pid==0)
  28.         {
  29.             //子进程
  30.             //execl("hello","hello",NULL);
  31.             execl("/usr/bin/ps", "ps","aux",NULL);
  32.             printf("I am child process, pid: %d\n", getpid());
  33.         }
  34.         for(int i=0;i<3;i++)
  35.         {
  36.             printf("i=%d, pid= %d\n",i, getpid());
  37.         }
  38.     return 0;
  39. }

【execlp】

  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. #include <unistd.h>
  11. #include <stdio.h>
  12. int main()
  13. {
  14.     //创建一个子进程,在子进程中执行exec函数族中的函数
  15.     pid_t pid=fork();
  16.     if(pid>0)
  17.     {
  18.         //父进程
  19.         printf("I am parent process, pid : %d\n",getpid());
  20.         sleep(1);
  21.     }
  22.     else if(pid==0)
  23.         {
  24.             //子进程
  25.             execlp("ps", "ps","aux",NULL);
  26.             printf("I am child process, pid: %d\n", getpid());
  27.         }
  28.         for(int i=0;i<3;i++)
  29.         {
  30.             printf("i=%d, pid= %d\n",i, getpid());
  31.         }
  32.     return 0;
  33. }

【execv】

  1. int execv(const char *path, char *const argv[]);
  2.         //argv是需要的参数的一个字符串数组
  3.         char* argv[]={"ps","aux",NULL};
  4.         execv("src/bin/ps",argv);

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

6.1 进程退出

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

6.2 孤儿进程

◼ 父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程 (Orphan Process)。

◼ 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束 了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。

◼ 因此孤儿进程并不会有什么危害。

6.3 僵尸进程

◼ 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法 自己释放掉,需要父进程去释放。

◼ 进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸 (Zombie)进程。

◼ 僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用, 但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进 程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。

6.4 进程回收

 7 wait/waitpid函数

【wait函数】

  1. /*
  2. #include <sys/types.h>
  3. #include <sys/wait.h>
  4. pid_t wait(int *wstatus);
  5.     功能:等待任意一个子进程结束,如果任意一个子进程结束了,函数会回收子进程的资源。
  6.     参数:int *wstatus
  7.         进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
  8.     返回值:
  9.         -成功:返回被回收的子进程的id
  10.         -失败:-1(所有的子进程都结束,调用函数失败)
  11. 调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出
  12. 或者收到一个不能被忽略的信号才被唤醒(相当于继续往下执行)
  13. 如果没有子进程了,函数立即返回,返回-1;如果子进程都已经结束了,
  14. 也会立即返回,返回-1
  15. */
  16. #include <sys/wait.h>
  17. #include <stdio.h>
  18. #include <sys/types.h>
  19. #include <unistd.h>
  20. #include <stdlib.h>
  21. int main()
  22. {
  23.     //有一个父进程,创建5个子进程
  24.     pid_t pid;
  25.     for(int i=0;i<5;i++)
  26.     {
  27.         pid=fork();
  28.         if(pid==0)
  29.         {
  30.             break;
  31.         }
  32.     }
  33.     if(pid>0)
  34.     {
  35.         //父进程
  36.         while(1)
  37.         {
  38.             printf("parant,pid=%d\n",getpid());
  39.             //int ret=wait(NULL);
  40.             int st;
  41.             int ret=wait(&st);
  42.             if(ret==-1)
  43.             {
  44.                 break;
  45.             }
  46.             if(WIFEXITED(st))
  47.             {
  48.                 //是不是正常退出
  49.                 printf("退出的状态码:%d\n",WEXITSTATUS(st));
  50.             }
  51.             if(WIFSIGNALED(st))
  52.             {
  53.                 //是不是异常终止
  54.                 printf("被哪个信号干掉了: %d\n",WTERMSIG(st));
  55.             }
  56.             printf("child die, pid =%d\n",ret);
  57.             sleep(1);
  58.         }
  59.     }
  60.     else if(pid==0)
  61.     {
  62.         //子进程
  63.         while(1)//while循环打开,通过kill -9 +id号杀死进程
  64.         {
  65.             printf("child,pid =%d\n",getpid());
  66.             sleep(1);
  67.         }
  68.         exit(1);//while去掉,正常退出,杀死
  69.     }
  70.     return 0;
  71. }

退出信息相关宏函数:

 【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.             =0options=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. int main() {
  26.     // 有一个父进程,创建5个子进程(兄弟)
  27.     pid_t pid;
  28.     // 创建5个子进程
  29.     for(int i = 0; i < 5; i++) {
  30.         pid = fork();
  31.         if(pid == 0) {
  32.             break;
  33.         }
  34.     }
  35.     if(pid > 0) {
  36.         // 父进程
  37.         while(1) {
  38.             printf("parent, pid = %d\n", getpid());
  39.             sleep(1);
  40.             int st;
  41.             // int ret = waitpid(-1, &st, 0);
  42.             int ret = waitpid(-1, &st, WNOHANG);
  43.             if(ret == -1) {
  44.                 break;
  45.             } else if(ret == 0) {
  46.                 // 说明还有子进程存在
  47.                 continue;
  48.             } else if(ret > 0) {
  49.                 if(WIFEXITED(st)) {
  50.                     // 是不是正常退出
  51.                     printf("退出的状态码:%d\n", WEXITSTATUS(st));
  52.                 }
  53.                 if(WIFSIGNALED(st)) {
  54.                     // 是不是异常终止
  55.                     printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
  56.                 }
  57.                 printf("child die, pid = %d\n", ret);
  58.             }
  59.            
  60.         }
  61.     } else if (pid == 0){
  62.         // 子进程
  63.          while(1) {
  64.             printf("child, pid = %d\n",getpid());    
  65.             sleep(1);      
  66.          }
  67.         exit(0);
  68.     }
  69.     return 0;
  70. }

8 进程间通信

8.1 进程间通讯概念

8.2  Linux 进程间通信的方式

8.3 匿名管道

 8.4 管道的特点

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

 8.6 管道的数据结构

 8.7 匿名管道的使用

 【父子进程通过匿名管道通信】

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

【匿名通信案例】

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

【管道的读写特点】

使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)

1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端

读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

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

3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程

向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。

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

总结:

读管道:

        管道中有数据,read返回实际读到的字节数。

        管道中无数据:

                写端被全部关闭,read返回0(相当于读到文件的末尾)

                写端没有完全关闭,read阻塞等待


写管道:

        管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)

        管道读端没有全部关闭:

                管道已满,write阻塞

                管道没有满,write将数据写入,并返回实际写入的字节数

【管道设置为非阻塞】

  1.  /*
  2.     设置管道非阻塞
  3.     int flags=fcntl(fd[0],F_GETFL);//获取原来的flag
  4.     flags |=O_NONBLOCK;            //修改flag的值
  5.     fcntl(fd[0],F_SETFL,flags);//设置新的flag
  6.  */
  7. #include <fcntl.h>
  8. #include <unistd.h>
  9. #include <sys/types.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  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.     pid_t pid=fork();
  24.     if(pid>0)
  25.     {
  26.         //父进程
  27.         printf("I am parent process, pid : %d\n",getpid());
  28.         //关闭写端
  29.         close(pipefd[1]);
  30.         //从管道的读取端读取数据
  31.         char buf[1024]={0};
  32.         int flags=fcntl(pipefd[0],F_GETFL);
  33.         flags |=O_NONBLOCK;          
  34.         fcntl(pipefd[0],F_SETFL,flags);
  35.         while(1){
  36.             int len=read(pipefd[0],buf,sizeof(buf));
  37.             printf("len : %d\n", len);
  38.             printf("parent recv : %s, pid : %d\n",buf,getpid());
  39.             sleep(2);
  40.         }
  41.     }
  42.     else if(pid==0)
  43.     {
  44.         //子进程
  45.         printf("I am child process, pid : %d\n",getpid());
  46.         //关闭读端
  47.          close(pipefd[0]);
  48.         char buf[1024]={0};
  49.         while(1)
  50.         {
  51.             //向管道写入数据
  52.             char *str="hello,我是你儿子!";
  53.             write(pipefd[1],str,strlen(str));
  54.             sleep(5);
  55.         }
  56.     }
  57.     return 0;
  58. }

8.8 有名管道

 8.9 有名管道的使用

 【有名管道的介绍及使用】

[mkfifo.c]

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

[read.c]

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

[write.c]

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

8.10 内存映射

 8.11 内存映射相关系统调用

#include <sys/mman.h>

◼ void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

功能:

        一个文件或其他对象映射到内存

参数:

       addr: 指定映射的起始地址,通常为NULL,由系统指定

       length:映射到内存的文件长度(字节)

       prot:映射区的保护方式,最常用的:

                (a)读:PROT_READ

                (b)写:PROT_WRITE

                (c)读写:PROT_READ | PROT_WRITE

       flags:映射区的特性,可以是:

                (a)MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享;

                (b)MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write),对此区域所做的修改不会写回原文件。

        fd:由open返回的文件描述符,代表要映射的文件;

        offset:以文件开始处的偏移量,必须是4k的整数倍,通常为0,表示从文件头开始映射

返回值:

        成功:返回创建的映射区首地址

        失败:MAP_FAILED宏

◼ int munmap(void *addr, size_t length);

功能:释放内存映射区

参数:

        addr:使用mmap创建的映射区的首地址

        length:映射区的大小

返回值:

        成功:0

        失败:-1

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <sys/mman.h>
  8. //存储映射
  9. int main()
  10. {
  11. int ret = -1;
  12. void* addr = NULL;
  13. //假设xiong.txt文件里的内容是【aaaaaaaaaa】
  14. //1.以读写的方式打开xiong.txt文件
  15. int fd = open("xiong.txt",O_RDWR);
  16. if(-1 == fd)
  17. {
  18. perror("open");
  19. exit(1);
  20. }
  21. //2.将文件映射到内存
  22. addr = mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
  23. if(addr == MAP_FAILED)
  24. {
  25. perror("mmap");
  26. exit(1);
  27. }
  28. printf("文件存储映射ok!!!\n"):
  29. //3.关闭文件
  30. close(fd);
  31. //4.写文件
  32. memcpy(addr,"12345",5);
  33. ///5.断开存储映射
  34. munmap(addr,1024);
  35. //此时xiong.txt文件里的内容变为【12345aaaaa】
  36. return 0;
  37. }

注意事项:
(1)创建映射区的过程中,隐含着一次对映射文件的读操作
(2)当MAP_SHARED时,要求:映射区的权限位<=文件打开的权限(出于对映射区的保护),而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。

4)特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
5)munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
6)如果文件偏移量必须为4K的整数倍。
7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

关于mmap函数的使用总结:

(1)第一个参数:写成NULL;

(2)第二个参数:要映射的文件大小>0

(3)第三个参数:PROT_READ、PROT_WRITE

(4)第四个参数:MAP_SHARED或MAP_PRIVATE

(5)第五个参数:打开的文件对应的文件描述符

(6)第六个参数:4k的整数倍,通常为0

9 信号

9.1 信号的概念

 9.2 Linux 信号一览表

【常用信号举例】

 

9.3 信号的 5 种默认处理动作

9.4 信号相关的函数

【kill函数介绍】

int kill(pid_t pid, int sig) ;
功能:给指定进程发送指定信号(不一定杀死)
参数:
        pid:取值有 4 种情况:
        pid >0:将信号传送给进程 ID 为pid的进程pid>0:
        pid =0:将信号传送给当前进程所在进程组中的所有进程
        pid =-1:将信号传送给系统内所有的进程。
        pid<-1:将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值

sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l(”为字母",不是数字1)进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。


返回值:
        成功:0
        失败:-1

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int main()
  7. {
  8. pid_t pid = fork();
  9. if(-1 == pid)
  10. {
  11. perror("fork");
  12. exit(1);
  13. }
  14. if(0 ==pid)
  15. {
  16. while(1)
  17. {
  18. printf("我子进程,我啥也不干,就是玩!!!\n");
  19. sleep(1);
  20. }
  21. }
  22. else
  23. {
  24. printf("父进程休息3s...\n");
  25. sleep(3);
  26. kill(pid,SIGINT);
  27. }
  28. return 0;
  29. }

【raise函数介绍】

#include <signal.h>


int raise(int sig);
功能: 给当前进程发送指定信号(自己给自己发),等价于 kill(getpid(),sig)
参数:
        sig: 信号编号
返回值:
        成功:0
        失败::非0值

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int mian()
  7. {
  8. int i =0;
  9. while(1)
  10. {
  11. printf("do working:%d\n",++i);
  12. if(i == 4)
  13. {
  14. raise(SIGTERM);
  15. }
  16. ++i;
  17. sleep(1);
  18. }
  19. return 0;
  20. }

【abort函数介绍】

#include <stdlib.h>


void abort(void);
功能: 给自己发送异常终止信号6,SIGABRT,并产生core文件,

                                                                                等价于kill(getpid(),SIGABRT);
参数:无


返回值: 无

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int mian()
  7. {
  8. int i =0;
  9. while(1)
  10. {
  11. printf("do working:%d\n",++i);
  12. if(i == 4)
  13. {
  14. abort();
  15. }
  16. ++i;
  17. sleep(1);
  18. }
  19. return 0;
  20. }

【alarm函数介绍】

#include <unistd.h>


unsigned int alarm(unsigned int seconds) ;

功能:;
        设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14) SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器;

        取消定时器alarm(0),返回旧闹钟余下秒数
参数:
        seconds; 指定的时问,以秒为单位

返回值:
        返回0或剩余的秒数

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int main()
  7. {
  8. unsigned int ret = 0;
  9. ret = alarm(5);
  10. printf("上一次闹钟剩下的时间是:%d\n",ret);
  11. sleep(2);
  12. ret = alarm(4);
  13. printf("上一次闹钟剩下的时间是:%d\n",ret);
  14. printf("按下任意键继续...\n");
  15. getchar();
  16. return 0;
  17. }

【setitimer函数介绍】

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //定时 7秒
  7. int main()
  8. {
  9. int ret = -1;
  10. struct itimerval tmo;
  11. //第一次触发时间
  12. tmo.it_value.tv_sec = 5;
  13. tmo.it_value.tv_usec = 0;
  14. //触发周期
  15. tmo.it_interval.tv_sec = 2;
  16. tmo.it_interval.tv_usec = 0;
  17. //设置定时器
  18. ret = setitimer(ITIMER_REAL,&tmo,NULL);
  19. if(-1 == ret)
  20. {
  21. perror("setitimer");
  22. return 1;
  23. }
  24. printf("按下任意键结束...\n");
  25. getchar();
  26. return 0;
  27. }

【signal函数】

#include <signal .h>


typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighander_t handler);

功能:
        注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。


参数:
        signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l(”l”为字母)进行相应查看。
        handler : 取值有 3 种情况:
                SIG_IGN: 忽略该信号
                SIG_DFL: 执行系统默认动作
                信号处理函数名:白定义信号处理函数,如: func

回调函数的定义如下:

        void func(int signo)

        {
                // signo 为触发的信号,为 signal() 第一个参数的值

        }
返回值;
        成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型
        头败:返回SIG_ERR

该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //信号处理函数1
  7. void func1(int signum)
  8. {
  9. printf("捕捉到信号:%d\n",signum);
  10. }
  11. //信号处理函数2
  12. void func2(int signum)
  13. {
  14. printf("捕捉到信号:%d\n",signum);
  15. }
  16. //信号处理函数
  17. int main()
  18. {
  19. //信号注册
  20. //Ctrl+c
  21. signal(SIGINT,func1);
  22. //Ctrl+\
  23. signal(SIGQUIT,func2);
  24. while(1)
  25. {
  26. getchar();
  27. }
  28. return 0;
  29. }
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. int i = 0;
  7. void func(int signum)
  8. {
  9. printf("Hello,world!!!---:%d\n",++i);
  10. }
  11. //第一次间隔5秒,之后间隔2秒,打印一次Hello,world!!!
  12. int main()
  13. {
  14. int ret = -1;
  15. struct itimerval tmo;
  16. //第一次触发时间
  17. tmo.it_value.tv_sec = 5;
  18. tmo.it_value.tv_usec = 0;
  19. //触发周期
  20. tmo.it_interval.tv_sec = 2;
  21. tmo.it_interval.tv_usec = 0;
  22. //捕捉信号SIGALRM
  23. signal(SIGALRM,func);
  24. //设置定时器
  25. ret = setitimer(ITIMER_REAL,&tmo,NULL);
  26. if(-1 == ret)
  27. {
  28. perror("setitimer");
  29. return 1;
  30. }
  31. printf("按下回车键结束...\n");
  32. getchar();
  33. return 0;
  34. }

9.5信号集

【自定义信号集函数】

为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。
这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友
信号集是一个能表示多个信号的数据类型,sigset_t set,set即一个信号集。既然是一个集合,就需要对集合进行添加/删除等操作。

相关函数说明如下:

#include <signal.h>

int sigemptyset(sigset_t *set);                        //将set集合置空


int sigfillset(sigset_t *set);                           //将所有信号加入set集合


int sigaddset(sigset_t *set,int signo);         //将signo信号加入到set集合 


int sigdelset(sigset t *set,int signo);            //从set集合中移除signo信号


int sigismember(const sigset_t *set,int signo); //判断信号是否存在

除sigismember外,其余操作函数中的set均为传出参数。sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //显示信号集
  7. void show_set(sigset_t *s)
  8. {
  9. for(int i =0; i<32; ++i)
  10. {
  11. if(sigismember(s,i)
  12. printf("1");
  13. else
  14. printf("0");
  15. }
  16. printf("\n");
  17. }
  18. int main()
  19. {
  20. //信号集集合
  21. sigset_t set;
  22. //清空集合
  23. sigemptyset(&set);
  24. show_set(&set);
  25. //将【所有】的信号添加到set信号集中
  26. sigfillset(&set);
  27. show_set(&set);
  28. //将信号2和信号3从set集合中移除
  29. sigdelset(&set,SIGINT);
  30. sigdelset(&set,SIGQUIT);
  31. show_set(&set);
  32. //将信号2添加到set集合中
  33. sigaddset(&set);
  34. show_set(&set);
  35. return 0;
  36. }

sigprocmask函数

        信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。
        所谓阻塞并不是禁止传送信号,而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
        我们可以通过 sigprocmask()修改当前的信号掩码来改变信号的阻塞情况。

#include <signal.h>


int sigprocmask(int how, const sigset_t *set,sigset_t *oldset);

功能:
        检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定而原先的信号阻塞集合由oldset保存;
参数:
        how:信号阻塞集合的修改方法,有 3种情况

                SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于mask = mask|set;

                SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于mask = mask & ~ set;
                SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照set中的信号重新设置信号阻塞集。相当于mask=set。
set :要操作的信号集地址。

        若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中oldset:保存原先信号阻塞集地址


返回值:
        成功:0
        失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //信号处理函数1
  7. void func1(int signum)
  8. {
  9. printf("捕捉到信号:%d\n",signum);
  10. }
  11. //信号处理函数2
  12. void func2(int signum)
  13. {
  14. printf("捕捉到信号:%d\n",signum);
  15. }
  16. //信号处理函数
  17. int main()
  18. {
  19. int ret = -1;
  20. //信号集
  21. sigset_t set;
  22. sigset_t oldset;
  23. //信号注册
  24. signal(SIGINT,func1); //Ctrl+c
  25. signal(SIGQUIT,func2); //Ctrl+\
  26. printf("按下回车键,阻塞信号2!!!\n");
  27. getchar();
  28. sigemptyset(&set);
  29. sigaddset(&set,SIGINT);
  30. //设置屏蔽编号为2的信号
  31. ret = sigprocmask(SIG_BLOCK,&set,&oldset);
  32. if(-1 == ret)
  33. {
  34. perroro("sigprocmask");
  35. exit(1);
  36. }
  37. printf("设置屏蔽编号为2的信号成功!!!\n");
  38. printf("按下回车键,解除编号为2的信号的阻塞!!!\n");
  39. getchar();
  40. //将信号屏蔽集设置为原来的集合
  41. ret = sigprocmask(SIG_SETMASK,&oldset,NULL);
  42. if(-1 == ret)
  43. {
  44. perroro("sigprocmask");
  45. exit(1);
  46. }
  47. printf("按下回车键退出...\n");
  48. getchar();
  49. return 0;
  50. }

sigpending函数

#include <signal.h>


int sigpending(sigset_t *set);

功能:读取当前进程的未决信号集

参数:
        set:未决信号集

返回值:
        成功:0
        失败:-1

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //信号处理函数1
  7. void func1(int signum)
  8. {
  9. printf("捕捉到信号:%d\n",signum);
  10. }
  11. //信号处理函数2
  12. void func2(int signum)
  13. {
  14. printf("捕捉到信号:%d\n",signum);
  15. }
  16. //信号处理函数
  17. int main()
  18. {
  19. int ret = -1;
  20. //信号集
  21. sigset_t set;
  22. sigset_t oldset;
  23. //信号注册
  24. signal(SIGINT,func1); //Ctrl+c
  25. signal(SIGQUIT,func2); //Ctrl+\
  26. printf("按下回车键,阻塞信号2!!!\n");
  27. getchar();
  28. sigemptyset(&set);
  29. sigaddset(&set,SIGINT);
  30. //设置屏蔽编号为2的信号
  31. ret = sigprocmask(SIG_BLOCK,&set,&oldset);
  32. if(-1 == ret)
  33. {
  34. perroro("sigprocmask");
  35. exit(1);
  36. }
  37. printf("设置屏蔽编号为2的信号成功!!!\n");
  38. /
  39. printf("按下回车键获取未决的信号...\n");
  40. getchar();
  41. //获取未决的信号集
  42. sigemptyset(&set);
  43. ret = sigpenging(&set);
  44. if(-1 == ret)
  45. {
  46. perroro("sigpenging");
  47. exit(1);
  48. }
  49. printf("处于未决的信号有:\n");
  50. //输出未决的信号
  51. for(int i =1; i<32; ++i)
  52. {
  53. sigismember(&set,i);
  54. {
  55. printf("%d ",i);
  56. }
  57. }
  58. putchar('\n');
  59. /
  60. printf("按下回车键,解除编号为2的信号的阻塞!!!\n");
  61. getchar();
  62. //将信号屏蔽集设置为原来的集合
  63. ret = sigprocmask(SIG_SETMASK,&oldset,NULL);
  64. if(-1 == ret)
  65. {
  66. perroro("sigprocmask");
  67. exit(1);
  68. }
  69. printf("按下回车键退出...\n");
  70. getchar();
  71. return 0;
  72. }

9.6信号捕捉

【信号处理方式:】

一个进程收到一个信号的时候,可以用如下方法进行处理:
(1)执行系统默认动作
        对大多数信号来说,系统默认动作是用来终止该进程
(2) 忽略此信号(丢弃)
接收到此信号后没有任何动作
(3)执行自定义信号处理函数(捕获)
        用用户定义的信号处理函数处理该信号


[注意]:SIGKILL和SIGSTOP 不能更改信号的外理方式,因为它们向用户提供了一种使进程终止的可靠方法。

内核实现信号捕捉过程:

       

 【sigaction函数

#include <signal.h>


int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:
        检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
        signum:要操作的信号。
        act:要设置的对信号的新处理方式(传入参数)
        oldact: 原来对信号的处理方式 (传出参数)。
如果act指针非空,则要改变指定信号的处理方式(设置),如果oldact 指针非空,则系统将此前指定信号的如理方式存入oldact。


返回值:
        成功:0
        失败:-1

struct sigaction结构体:

struct sigaction {
        void(*sa_handler)(int);         //旧的信号处理函数指针

        void(*sa_sigaction)(int,siginfo_t *,void *); //新的信号处理函数指针
        sigset_t  sa_mask;                //信号阻塞集
        int    sa_flags;                      //信号处理的方式

        void(*sa_restorer)(void);       //已弃用

};

(1)sa_handler、sa_sigaction: 信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:
        (a) SIG_IGN:忽略该信号
        (b) SIG_DFL::执行系统默认动作
        (c)处理函数名:自定义信号处理函数
(2)sa_mask: 信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。

(3)sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:

        -SA_RESTART:使被信号打断的系统调用自动重新发起 (已经废弃);
        -SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号;
        -SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程;
        -SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号;
        -SA_RESETHAND:信号处理之后重新设置为默认的处理方式;
        -SA_SIGINFQ:使用 sa_sigacion 成员而不是sa_handler 作为信号处理函数;

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. void func1(int signo)
  7. {
  8. printf("捕捉到信号:%d\n",signo);
  9. }
  10. void func2(int signo,siginfo_t *info,void* context)
  11. {
  12. printf("Catch to the signal:%d\n",signo);
  13. }
  14. int main()
  15. {
  16. int ret = -1;
  17. #if 0
  18. struct sigaction act;
  19. act.sa_handler= func1;
  20. act.flags = 0;
  21. #else
  22. struct sigaction act;
  23. act.sa_sigaction = func2;
  24. act.sa_flags = SA_SIGINFO;
  25. #endif
  26. ret = sigaction(SIGINT,&act,NULL);
  27. if(-1 == ret)
  28. {
  29. perror("sigaction");
  30. exit(1);
  31. }
  32. while(1)
  33. {
  34. }
  35. return 0;
  36. }

9.7可重入函数、不可重入函数

如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。


满足下列条件的函数多数是不可重入 (不安全) 的:
        -函数体内使用了静态的数据结构,
        -函数体内调用了malloc) 或者 free函数(谨慎使用堆)
        -函数体内调用了标准I/O函数
相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。那么什么是可重入函数呢? 所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。

保证函数的可重入性的方法
        -在写函数时候尽量使用局部变量 (例如寄存器、栈中的变量) :
        -对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。

9.8SIGCHLD信号

(1) 子进程终止时;
(2) 子进程接收到SIGSTOP信号停止时;
(3)子进程处在停止态,接受到SIGCONT后唤醒时;

 如何避免僵尸进程:
(1)最简单的方法,父进程通过 wait() 和 waitpid()等函数等待子进程结束,但是,这会导致父进程挂起;

(2)如果父进程要处理的事情很多,不能够挂起,通过signal()函数人为处理信号SIGCHLD,只要有子进程退出自动调用指定好的回调函数,因为子进程结束后,父进程会收到该信号SIGCHLD可以在其回调函数里调用wait()或 waitpid()回收。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. void func(int signo)
  7. {
  8. printf("捕捉信号signal:%d\n");
  9. printf("有子进程退出...\n");
  10. while((waitpid(-1,NULL,WNOHANG)) >0)
  11. {
  12. }
  13. }
  14. int main()
  15. {
  16. pid_t pid;
  17. struct sigaction act;
  18. act.sa_handler = func;
  19. act.sa_flags = 0;
  20. sigaction(SIGCHLD,&act,NULL);
  21. pid = fork();
  22. if(-1 == pid)
  23. {
  24. perror("fork");
  25. exit(1);
  26. }
  27. else if(0 == pid)
  28. {
  29. sleep(3);
  30. printf("I'm 子进程,pid :%d,I'm exiting.\n",getpid());
  31. exit(0);
  32. }
  33. else if(pid > 0)
  34. {
  35. while(1)
  36. {
  37. printf("父进程working...\n");
  38. sleep(1);
  39. }
  40. }
  41. return 0;
  42. }

10 进程组和守护进程

会话的概念:

 创建会话注意事项:

(1)调用进程不能是进程组组长,该进程变成新会话首进程(session header)
(2)该调用进程是组长进程,则出错返回
(3)该进程成为一个新进程组的组长进程
(4)需有root权限(ubuntu不需要)
(5)新会话丢弃原有的控制终端,该会话没有控制终端
(6)建立新会话时,先调用fork,父进程终止,子进程调用setsid

【守护进程介绍:】

        守护进程(Daemon Process),也就是通常说的Daemon 进程(精灵进程),是Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。般采用以d结尾的名字。
        守护进程是个特殊的孤儿进程这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
Linux的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd等;

【守护进程模型】

(1)创建了进程,父进程退出(必须)
        -所有工作在子进程中进行形式上脱离了控制终端
(2)在子进程中创建新会话(必须)
        -setsid0函数
        -使子进程完全独立出来,脱离控制
(3)改变当前目录为根目录(不是必须)
        -chdir函数
        -防止占用可卸载的文件系统
        -也可以换成其它路径
(4)重设文件权限掩码(不是必须
        -umask()函数
        -防止继承的文件创建屏蔽字拒绝某些权限
        -增加守护进程灵活性
(5)关闭文件描述符(不是必须)
        -继承的打开文件不会用到,浪费系统资源,无法卸载

(6)开始执行守护进程核心工作(必须)

        -守护进程退出处理程序模型

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. //创建守护进程
  7. int main()
  8. {
  9. int ret = -1;
  10. //创建子进程,父进程退出
  11. pid_t pid = fork();
  12. if(-1 == pid)
  13. {
  14. perror("fork");
  15. exit(1);
  16. }
  17. if(pid >0)
  18. {
  19. //父进程退出
  20. exit(0);
  21. }
  22. //2.创建新的会话,完全脱离控制终端
  23. pid = setsid();
  24. if(-1 == pid)
  25. {
  26. perror("setsid");
  27. exit(1);
  28. }
  29. //3.改变当前工作目录
  30. ret = chdir("/");
  31. if(-1 == ret)
  32. {
  33. perror("chdir");
  34. exit(1);
  35. }
  36. //4.设置权限掩码
  37. umask(0);
  38. //5.关闭文件描述符
  39. close(STDIN_FILENO);
  40. close(STDOUT_FILENO);
  41. close(STDERR_FILENO);
  42. //6.执行核心的任务
  43. //每隔1秒输出当前时间到/tmp/txt.log文件中
  44. while(1)
  45. {
  46. system("date >> /tmp/txt.log");
  47. sleep(1);
  48. }
  49. return 0;
  50. }

【获取当前系统时间相关函数】

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. #define SIZE 64
  8. int main()
  9. {
  10. time_t t = -1;
  11. char file_name[SIZE];
  12. struct tm *pT = NULL;
  13. //获取时间---秒
  14. t = time(NULL);
  15. if(-1 == t)
  16. {
  17. perror("time");
  18. return 1;
  19. }
  20. printf("t: %ld\n",t);
  21. printf("ctime: %s\n",ctime(&t);
  22. //转化为时间
  23. pT = localtime(&t);
  24. if(NULL == pT)
  25. {
  26. printf("localtime failed...\n");
  27. return 1;
  28. }
  29. //转化为文件名
  30. memset(file_name,0,SIZE);
  31. sprintf(file_name,"%s %d%d%d%d%d%d.log","touch",pT->tm_year +1900,pT->tm_mon
  32. +1,pT->tm_mday,pT->tm_hour,pt->tm_min,pt->tm_sec);
  33. printf("file_name: %s\n", file_name);
  34. //把file_name中的内容作为指令在终端输出
  35. system("file_name");
  36. return 0;
  37. }

作业:守护进程每隔1秒,在/home/log目录下创建以时间命名的文件

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. define SIZE 64
  8. //创建守护进程
  9. int main()
  10. {
  11. int ret = -1;
  12. time_t t = -1;
  13. char file_name[SIZE];
  14. struct tm* pT = NULL;
  15. //创建子进程,父进程退出
  16. pid_t pid = fork();
  17. if(-1 == pid)
  18. {
  19. perror("fork");
  20. exit(1);
  21. }
  22. if(pid >0)
  23. {
  24. //父进程退出
  25. exit(0);
  26. }
  27. //2.创建新的会话,完全脱离控制终端
  28. pid = setsid();
  29. if(-1 == pid)
  30. {
  31. perror("setsid");
  32. exit(1);
  33. }
  34. //3.改变当前工作目录
  35. ret = chdir("/");
  36. if(-1 == ret)
  37. {
  38. perror("chdir");
  39. exit(1);
  40. }
  41. //4.设置权限掩码
  42. umask(0);
  43. //5.关闭文件描述符
  44. close(STDIN_FILENO);
  45. close(STDOUT_FILENO);
  46. close(STDERR_FILENO);
  47. //6.执行核心的任务
  48. while(1)
  49. {
  50. t = time(NULL);
  51. pT = localtime(&t);
  52. if(NULL == pT)
  53. {
  54. printf("localtime failed...\n");
  55. return 1;
  56. }
  57. memset(file_name,0,SIZE);
  58. sprintf(file_name,"%s%d%d%d%d%d%d.log","touch /home/log/",pT->tm_year
  59. +1900,pT->tm_mon +1,pT->tm_mday,pT->tm_hour,pt->tm_min,pt->tm_sec);
  60. system(file_name);
  61. sleep(1);
  62. }
  63. return 0;
  64. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/333276
推荐阅读
相关标签
  

闽ICP备14008679号