这段时间因为别的组在用我们代码的时候发现ctrl+c无法使进程退出,表现出来的现象是我们的init函数有问题,只要加上我们的init函数,那么遇到ctrl+c的操作时,进程就是无法退出。最终通过gdb调试,发现是另一个组的基于shm和udp的消息发布订阅模块造成的,但是因为没有源码,只能定位到是析构函数有问题。因为开发人员休假无法到位,于是趁此机会把跨进程的信号通信机制了解了一下。毕竟以前都是在书上看的,纸上得来终觉浅。
它是一种特殊的IPC(进程间通信)机制,它是系统里面已经设计好了的,我们只能去使用它,且是一种异步通信方式。系统里面自带的信号可以用kill -l列出来,总共64个。

比较常用的信号:
kill和killall都是用来发信号给某个(些)进程的,和我们原来理解的“杀死”有点区别的,不能光看到kill -9 就以为kill是杀死进程的意思哈。
注:可先使用ps -ef查看目前系统正在运行的所有进行及其进程号等进程信息
kill除了是系统命令外,还是系统提供的函数。用户可以在函数中使用。
函数原型:
- #include
- #include
-
- int kill(pid_t pid, int sig);
-
- 函数形参:
- pid:想要用于接收发送出去的信号的进程pid号
- >0:将信号发送给指定的进程;
- =0:将信号发送给当前的进程组;
- =-1:将信号发送给每一个有权限接收这个信号的进程;
- <-1:这个pid=某个进程组的ID取反(-12345)
- sig:需要发送的信号值,0表示不发送任何信号
-
- 函数返回值:
- 成功返回0
- 失败返回-1
示例:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- //创建一个子进程
- pid_t id = fork();
-
- if(id<0)
- {
- perror("fork failed");
- return -1;
- }
- else if(id==0)
- {
- printf("I am %d\n",getpid());
- sleep(10);
- //把自己杀掉
- kill(getpid(),9);
- printf("i am child and pid = %d\n",getpid());
- }
- else
- {
- printf("I am parent, pid= %d\n",getpid());
- sleep(20);
- }
- //退出程序并且刷新缓冲区
- exit(0);
- }
-
可以看到子进程自己把自己杀死了,并变成了“僵尸进程(defunct)”,后面的语句无法输出。之后等待10s后,父进程退出,系统回收资源,进程资源释放,无法再显示这两个进程。

进程对信号的操作主要有:
注:9,18,19这几个信号不可捕捉,忽略,阻塞;否则所有进程都忽略了这几个函数所有进程将变成守护进程,当进程创建到达最大数时将会导致系统奔溃
函数原型:
- #include
- typedef void (*sighandler_t)(int);
- sighandler_t signal(int signum, sighandler_t handler);
-
- 函数形参:
- typedef void (*sighandler_t)(int):宏定义了一个函数指针,返回值类型为void,形参类型为:int
- signum:需要捕捉的信号值
- handler:本质是一个指针函数,接受接收到相应信号执行的函数,也可使用:
- SIG_IGN —》忽略
- SIG_DFL —》执行默认动作
- 函数返回值:
- 成功:返回前一个这个信号处理的回调函数的指针
- 出错:出现错误时,返回SIG_ERR
当signal(int signum, sighandler_t handler)的handler设置为:
示例:
这里实际上是更改了系统设计的信号值为2、3、4的处理函数,也就是当程序遇到2、3、4信号值时,不再使用默认的处理方式。例如2号sigINT,也就是ctrl+c。如果我们按下ctrl+c或者执行kill -2 pid,这两个操作是否还是像往常一样把进程杀死呢?
- #include
- #include
- #include
- #include
- #include
-
- void handel(int num)
- {
- if(num == 2)
- {
- printf("catch the 2 signal\n");
- }
-
- else if(num == 3)
- {
- printf("catch the 3 signal\n");
- }
- else if(num == 4)
- {
- printf("catch the 4 signal\n");
- }
- }
-
- int main()
- {
- //捕捉2,3,4号信号,并且执行相对应的函数
- signal(2,handel);
- signal(3,handel);
- signal(4,handel);
- //创建一个子进程
- while(1)
- {
- printf("hello world\n");
- sleep(1);
- }
-
- //退出程序并且刷新缓冲区
- exit(0);
- }
-
-
-

下面我们在程序运行过程中使用ctrl+c试一下,也就是左图中的^Ccatch the 2 signal那一行。我们发现进程并不退出,说明我们使用ctrl+c和kill -2 效果一样。
阻塞时,该信号不执行,等待解除阻塞再执行该信号,并且如果是1-31号的信号,发送多个相同信号也只会执行一次。常用的阻塞信号函数原型:
- //一个信号列表(集合)
- sigset_t set;
-
- //信号值
- int signum;
-
- //清空一个信号列表(集合)
- int sigemptyset(sigset_t *set);
-
- //把所有的信号都加入一个信号列表(集合)
- int sigfillset(sigset_t *set);
-
- //把某一个信号都加入一个信号列表(集合)
- int sigaddset(sigset_t *set, int signum);
-
- //把某一个信号从一个信号列表(集合)删除
- int sigdelset(sigset_t *set, int signum);
-
- //判断一个信号是否在一个信号列表(集合)
- int sigismember(const sigset_t *set, int signum);
-
- //把一个信号列表(集合)设置为阻塞状态或解除阻塞
- // how:(1)SIG_BLOCK:在原有的阻塞信号中再添加信号列表(集合)中的信号
- // (2)SIG_UNBLOCK:在原有阻塞信号基础上(在阻塞信号中找)解除信号列表(集合)中的信号
- // (3)SIG_SETMASK:把原有的阻塞信号全部替换成信号列表(集合)中的信号
- //
- //const sigset_t *set:新的信号列表(集合)
- //sigset_t *oldset:原有的信号列表(集合)
- int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
- 函数的返回值:
- 成功:sigismember返回1,其他函数均为返回0
- 失败:sigismember返回0,其他函数均为返回-1
示例:
- #include
- #include
- #include
- #include
- #include
-
-
- int main()
- {
- //定义一个信号列表(集合)
- sigset_t set;
-
- //清空一下信号列表(集合)
- sigemptyset(&set);
-
- //添加信号到信号列表
- sigaddset(&set,2);
- sigaddset(&set,3);
- sigaddset(&set,4);
- //设置信号列表为阻塞
- sigprocmask(SIG_SETMASK, &set, NULL);
-
- int i=0;
- while(1)
- {
- //判断i是否为20,如果是就解除阻塞
- if(i==20)
- {
- //解除信号阻塞
- sigprocmask(SIG_UNBLOCK, &set, NULL);
- }
-
- //最后看会不会打印这行
- printf("hello world\n");
- sleep(1);
-
- //每次加1
- i++;
- }
-
- //退出程序并且刷新缓冲区
- exit(0);
- }
-
-
-
-

另外,误操作学到一个高级操作,在本目录下搜索字符串:

带上通配符能搜字符串匹配的内容,不带通配符就是列出进程啦。