利用信号来处理子进程的回收是非常方便和高效的,因为所有的工作你都可以交给内核,当子进程终止时,会发出一个信号,一旦你注册的信号捕捉器捕捉到了这个信号,那么就可以去回调自己的函数(处理处理子进程的函数),去回收子进程了。
在引入函数指针和回调函数之前我们先介绍一下alarm()闹钟函数!
alarm()闹钟函数
alarm:设置定时器(闹钟)。在指定秒数(seconds)后,内核会给当前进程发送14)SIGALRM信号(前面的博客有所介绍)。进程收到该信号,默认动作终止。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
返回值: 返回0或剩余的秒数,这个函数调用不存在失败!!!!
每个进程有且只有一个定时器!
用大白话来描述上述图片就是:你在里面传入的正整数参数就是你要定时的秒数,在此期间不管你的进程处于何种状态(就绪、运行、挂起(阻塞、暂停)、终止、僵尸.)alarm都会进行计时,当计时完成就会发送 14)SIGALRM信号------>进程收到该信号,默认动作终止(也就是终止当前进程不做任何处理。)
但是如果你注册了信号捕捉器那么就会进行函数回调,在信号捕捉器接收到14号信号之后就回调你自己定义的函数,这样你的程序不但不会终止,而且还能进行子进程的回收。
取消定时器: alarm(0),返回旧闹钟余下秒数。
小补充:
除了alarm()之外还有一个计时函数,setitimer()---->精确度更高,可代替alarm函数。精度微秒us,可以实现周期定时。各位朋友能掌握alarm()已经足够了,如果想要了解更多的可以自行百度,博主在这里就不在介绍了。
你说那么多都还没有讲到信号捕捉器啊!!!!
莫急,在正式开始信号捕捉起之前我们还需要了解一下函数指针和回调函数(这个是必须要学会的,不然后面的内容你是看不懂的,这可不是博主在乱说)所以,骚年加油吧!!
好啦,博主在这里举一个小例子来给大家讲一下(极速入门!)。
请看代码!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int Function_pointer(const void *a, const void *b) {
printf("成功调用 函数指针啦,你好骚气哦! \n");
return 1;
}
int main(void) {
int x = 10;
int y = 20;
//函数指针的定义 把函数声明移过来,把函数名改成 (* 函数指针名)
int(*fp)(const void *, const void *);
/*贝尔实验室的C和UNIX的开发者采用第1种形式,而伯克利的UNIX推广者却采用第2
种形式ANSI C 兼容了两种方式*/
fp = &Function_pointer; //
(*fp)(&x, &y); //第1种,按普通指针解引的放式进行调用,(*fp) 等同于compare_int
fp(&x, &y); //第2种 直接调用
system("pause");
return 0;
}
下面就让我们理一理函数指针吧。
首先大家要明白函数本身就是地址是一个指针。
那么肯定就可以有一个指针来指向我们的函数了,这个指针就是我们所说的函数指针(这个我们宿舍的学霸(刘老板)都有点懵比),那么我们看看函数指针有啥特别的。
- 在子函数的声明和正常的函数是一样的!!!
- 想要快速创建一个函数指针就最佳方法就是把函数声明ctrl+c、ctrl+v复制下来,然后把函数的名字改成 (* 自定义的函数指针名),就好了。里面的形参你想加变量名就加变量名,纯属个人喜好。
- 接下来只需要让你自定义的函数指针名指向你之前的函数声明即可(18行)。
- 调用有两种方法,见代码。这样就是我们的函数指针了
大家好好看我上面的实例看懂函数指针应该是不难了,接下来我们就来看看回调函数吧。(不要急,书上不是说了吗,这两个不懂的话是看不懂后面的内容的,这可不是博主不想马上讲解信号捕捉器哟)
欢迎来到 回调函数联盟(借用德莱文的话…)
我们的函数指针是一个指针变量,那么它能不能作为一个函数的参数呢?答案时是肯定的。在我们的操作系统源码,以及在linux中许多系统调用中,有许多这样的函数我们等下要接触到的 信号捕捉器就会用到,但是我们先来一个引入吧!!
观看博客的小可爱:我去,博主,你咋一直在搞引入啊?,我要直接看回调!!
博主:我也是为了大家好呀.
观看博客的小可爱:那好吧最后一次了。
博主:好的
好了,回到正式上来,该引入我们的回调函数了(需要把之前的函数小小的改造一下)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int Function_pointer(const void *a, const void *b) {
printf("成功回调 Function_pointer 啦,你好骚气哦! \n");
int *a1 = (int *)a;
int *b1 = (int *)b;
return *a1 - *b1;
}
int main(void) {
int x = 10;
int y = 20;
//函数指针的定义 把函数声明移过来,把函数名改成 (* 函数指针名)
int(*fp)(const void *x, const void *y);
/*贝尔实验室的C和UNIX的开发者采用第1种形式,而伯克利的UNIX推广者却采用第2
种形式ANSI C 兼容了两种方式*/
fp = &Function_pointer; //
//(*fp)(&x, &y); //第1种,按普通指针解引的放式进行调用,(*fp) 等同于compare_int
//fp(&x, &y); //第2种 直接调用
int arr[] = { 2, 10, 30, 1, 11, 8, 7, 111, 520 };
qsort(arr, sizeof(arr) / sizeof(int), sizeof(int), Function_pointer);
for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
printf(" %d", arr[i]);
}
system("pause");
return 0;
}
讲解:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当 这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调 用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 我们C语言库函数中就有一个典型的案例:qsort 函数(就是我们的26行),
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
第一个参数: void *base, size_t num:
--->泛型指针,这样就能接受任何值
第二个参数: size_t width:
--->大小
第三个参数:int (__cdecl *compare )(const void *elem1, const void *elem2 )
-->看到了吧 函数指针"__cdecl这个不用管,在你声明函数的时候系统给你处理好了"
希望大家通过上面的代码能明白回调函数。
---->其实就是,在函数里调用函数去执行你要执行的那个函数。(希望没把大家搞晕。)我还特意把我们之前的函数指针调用给注释了哟,哈哈哈!
前戏已经做足相信大家也已经迫不及待了吧,哈哈,我们的信号捕捉器正式开!!!
我们已经了解到了 信号、alarm()闹钟函数、函数指针和回调函数了,再看之前的图片。
是不是不在瑟瑟发抖啦。哈哈哈,其实只要你认真看了我之前的信号捕捉器开端篇,书上给你介绍的宏(有点没说清楚),理解也会更深刻!
再给出我们之前的signal()函数
#include<signal.h>
void (*signal)(int signo, void(*func)(int)))(int);
函数名: signal
参数: int signo, void(* func)(int)
返回类型:参数类型为 int 型, 返回 void 型函数指针。
--->为了在产生信号时调用, 返回之前注册的函数指针
现在看这个是不是有些感觉了,哈哈。
调用上述函数时,第一个参数的信息,就是博主前面的博客提到的那些信号类型。
第二个参数为特殊情况下要调用的函数的地址值(指针==函数名),当发生第一个参数的情况下,就会调用第二个参数所指向的函数了。
下面给出signal()函数中用到的一些信号(部分),更多的可以在我的信号的介绍博客看到详细的信号处理。
- SIGALRM: 已到通过调用 alarm 函数注册的时间
- SIGINT:输入 CTRL+C
- SIGCHIL:子进程终止
了解到这些我们就能去看一看书本中的代码了!!!!
下面是更加详细的解释,相信大家也能看懂了吧。
博主主要是为了让大家能明白书中的信号捕捉器而讲了那么多,也相信大家已经明白了。所以博主还要告诉大家一件事,signal()函数用到的不多,更多的是另一个函数来处理信号那就是:sigaction()函数。
朋友们坚持不!博主保证,这是最后一个了
sigaction()函数
前面我们讲到的内容已经足以用来防止僵尸进程生成的代码。之所以博主还要介绍sigaction()函数是因为它类似于signal()函数,而且完全可以代替后者,也更稳定(主要是书上介绍到了),之所有更稳定,是因为如下原因:
"signal()函数在UNIX系列的不同操作系统中可能存在区别,但sigaction()函数完全相同!"
实际上现在很少使用 signal()函数编写程序,它只是为了保持对旧程序的兼容(别崩溃啊)。下面介绍 sigaction()函数,单只脚接它的功能,展开来说太复杂了。
信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact); 成功:0;失败:-1,设置errno
第一个参数 int signo:
--->与signo相同,传递信号信息(我之前的博客写的很全)
第二个参数 const struct sigaction *act:
--->传入参数,新的处理方式。
第三个参数 struct sigaction *oldact:
--->传出参数,旧的处理方式。
返回值:
成功: 0
失败: -1,设置 errno
相信大家也看到了,上面有结构体
不能逃避呀!(这篇博客确实很难看下去,我自己写这个也很遭罪的)
来看看吧
struct sigaction结构体(增加了书上没有的)
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);
};
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)
重点掌握:(也就是我们书上的)
sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。(也可以设置为0)
sa_flags:通常设置为0,表使用默认属性。
书上所说:
书上之所以让把 sa_mask也设置0里面有牵扯到了,
信号屏蔽字 与 未决信号集(博主也不展开说了,比较繁琐)
解决这个函数的难点吧。其实大家应该知道没有难点了。
第一个参数就是和signal的第一个参数一样
第二个参数的结构体我们只需要管三个参数就行而且:第二三个参数
sigset_t sa_mask;
int sa_flags;
都可直接设置0
所以结构体我们只用管第一个参数:
void (*sa_handler)(int);
这不就是我们的signal 得第二个参数吗?
第三个参数:直接传入之前的处理方式即可,如果不关心可以直接传NULL(传入之前的方式是为了恢复以前的处理方式)。
信号捕捉器讲完了========处理子进程回收的终极方法也将完了。下面看看书上的代码就可以结束我们这篇博客了
是不是好烦啊!!!sigempty()函数书上也没讲!!!
博主再来拓展一下(不要怪书上,谢了介绍的话又要增加大家的负担了,因为涉及信号集!)
信号集设定
sigset_t set; // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 将某个信号集清0 成功:0;失败:-1
int sigfillset(sigset_t *set); 将某个信号集置1 成功:0;失败:-1
int sigaddset(sigset_t *set, int signum); 将某个信号加入信号集 成功:0;失败:-1
int sigdelset(sigset_t *set, int signum); 将某个信号清出信号集 成功:0;失败:-1
int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中 返回值:在集合:1;不在:0;出错:-1
sigset_t类型的本质是位图(由 1 0 组成)。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
好啦博客结束了,细细看完这篇博客在看书上的内容应该不成问题了。