本文共 6301 字,大约阅读时间需要 21 分钟。
在引入函数指针和回调函数之前我们先介绍一下alarm()闹钟函数!
alarm:设置定时器(闹钟)。在指定秒数(seconds)后,内核会给当前进程发送14)SIGALRM信号(前面的博客有所介绍)。进程收到该信号,默认动作终止。
#includeunsigned int alarm(unsigned int seconds);返回值: 返回0或剩余的秒数,这个函数调用不存在失败!!!!
取消定时器: alarm(0),返回旧闹钟余下秒数。
除了alarm()之外还有一个计时函数,setitimer()---->精确度更高,可代替alarm函数。精度微秒us,可以实现周期定时。各位朋友能掌握alarm()已经足够了,如果想要了解更多的可以自行百度,博主在这里就不在介绍了。
莫急,在正式开始信号捕捉起之前我们还需要了解一下函数指针和回调函数(这个是必须要学会的,不然后面的内容你是看不懂的,这可不是博主在乱说)所以,骚年加油吧!!
好啦,博主在这里举一个小例子来给大家讲一下(极速入门!)。
请看代码!#include#include #include 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;}
首先大家要明白函数本身就是地址是一个指针。
那么肯定就可以有一个指针来指向我们的函数了,这个指针就是我们所说的函数指针(这个我们宿舍的学霸(刘老板)都有点懵比),那么我们看看函数指针有啥特别的。大家好好看我上面的实例看懂函数指针应该是不难了,接下来我们就来看看回调函数吧。(不要急,书上不是说了吗,这两个不懂的话是看不懂后面的内容的,这可不是博主不想马上讲解信号捕捉器哟)
我们的函数指针是一个指针变量,那么它能不能作为一个函数的参数呢?答案时是肯定的。在我们的操作系统源码,以及在linux中许多系统调用中,有许多这样的函数我们等下要接触到的 信号捕捉器就会用到,但是我们先来一个引入吧!!
观看博客的小可爱:我去,博主,你咋一直在搞引入啊?,我要直接看回调!!
好了,回到正式上来,该引入我们的回调函数了(需要把之前的函数小小的改造一下)。
#include#include #include 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()函数
#includevoid (*signal)(int signo, void(*func)(int)))(int);函数名: signal参数: int signo, void(* func)(int)返回类型:参数类型为 int 型, 返回 void 型函数指针。--->为了在产生信号时调用, 返回之前注册的函数指针现在看这个是不是有些感觉了,哈哈。
调用上述函数时,第一个参数的信息,就是博主前面的博客提到的那些信号类型。
第二个参数为特殊情况下要调用的函数的地址值(指针==函数名),当发生第一个参数的情况下,就会调用第二个参数所指向的函数了。下面给出signal()函数中用到的一些信号(部分),更多的可以在我的看到详细的信号处理。
博主主要是为了让大家能明白书中的信号捕捉器而讲了那么多,也相信大家已经明白了。所以博主还要告诉大家一件事,signal()函数用到的不多,更多的是另一个函数来处理信号那就是: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 { 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,表使用默认属性。
书上所说:
解决这个函数的难点吧。其实大家应该知道没有难点了。
第一个参数就是和signal的第一个参数一样
所以结构体我们只用管第一个参数:
void (*sa_handler)(int); 这不就是我们的signal 得第二个参数吗?信号集设定 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 组成)。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
好啦博客结束了,细细看完这篇博客在看书上的内容应该不成问题了。
转载地址:http://cgdwz.baihongyu.com/