深入理解Linux kernel(内核)中的signal函数
在Linux内核代码中,有一个信号处理绑定器函数signal,它到原型定义如下,通过man 2 signal可以查看其原型。#include typedef void (*sighandler_t)(int); (1)sighandler_t signal(int signum,sighandler_t handler); (2)第一句是包含头文件,第二句是类型定义,第
在Linux内核代码中,有一个信号处理绑定器函数signal,它到原型定义如下,通过man 2 signal可以查看其原型。
#include <signal.h>
typedef void (*sighandler_t)(int); (1)
sighandler_t signal(int signum,sighandler_t handler); (2)
第一句是包含头文件,第二句是类型定义,第三句才是signal函数到真正声明。扩展声明后,真正到声明为
void (*signal(int signum,void (*sighandler)(int)))(int); (3)
第(3)看起来比第(1)和(2)句更难理解,首先从(1)(2)下手。
(1)表示sighandler_t是自定义类型,它其实是一种函数指针类型,它指向到函数到形参为int,返回值为void。
(2)表示signal函数到形参有一个int型signum,以及一个sighandler_t型到形参handler,返回一个sighandler_t型到值,由(1)可以知道:sighandler_t是函数指针类型,所以signal到第二个形参是一个函数指针,并且signal函数返回一个函数指针。它们指向到函数类型都是具有一个int形参,返回为void到函数。
迷惑:对于signal函数,对于初学者来说,不仅是语法上到迷惑,还有应用上到迷惑,下面结合我自己到理解,举例说明怎样理解signal函数。
代码think.c-----------------------------------------------------------------------------------
#include<stdio.h>
/*定义一个函数*/
void func(int a){
printf("in func a=%d/n",a);
}
/*声明一个函数指针*/
void (*pfunc)(int);
/*定义我们自己到my_signal函数*/
void (*my_signal(int sig,void (*handler)(int h)))(int c){
printf("in my_signal/n");
/*想一下下面一句,对吗?*/
return handler;
}
int main(){
printf("before invoke 'void func(int)'/n");
func(1); /*(4)*/
pfunc=func;
printf("before invoke 'void func(int) by function pointer'/n");
pfunc(2); /*(5)*/
printf("before invoke 'my_signal(int sig,void (*handler)(int h)))'/n");
pfunc=my_signal(2,func); /*(6)*/
printf("before invoke 'the function pointer return by my_signal'/n");
pfunc(4); /*(7)*/
printf("/nbefore invoke 'my_signal(int sig,void (*handler)(int h)))(int c)'/n");
my_signal(5,func)(6); /*(8)*/
/* (9)
printf("the return of my_signal(6,func)(6) is %X/n",(unsigned)&my_signal(5,func)(6));
*/
return 0;
}
上面到代码在gcc4.4.1中编译运行都没有错误。
迷惑1:从(2)和(3)对比来看,二者在语法上完全等价,但是从调用上看呢?
先看声明(2):
(2)的感觉就像是一个很普通到函数调用,我们将函数指针换成一个指向基本类型到指针如int *,(2)就变成了(2a)int *signal(int signum,int *handler);
(2a)是个很普通到函数,所以调用它到时候自然而然就是signal(1,pointer);也就是think.c中main函数到调用(6)。
再看声明(3):
(3)完全使用基本类型定义来表示到函数,所以看起来颇复杂,拨开整个函数,将signal(...)省略为signal(),那么(3)变成了(3a)void (*signal())(int);进一步,我们直接使用一个指针来表示signal()函数返回到指针,那么就变成了(3b)void (*psignal)(int);这样就简单了,这个函数的调用极其简单了,也就类似think.c中到调用(8)。
现在问题出来了,发现调用(6)和(8)截然不同了吗?
而且,看看函数my_signal的定义方式,一眼看上去还感觉不对劲呢,函数不是void吗?怎么还返回一个函数指针?
其实,当你把(3)简化到(3b)到时候,就会发现,其实signal不是最外层到函数,而整个my_signal函数到定义并不是最外层函数,它返回的指针是指向一个函数的,所以my_signal函数会返回一个指针!而不是void。而当你以方式(6)调用my_signal函数的时候,它会返回一个函数指针,而当你用方式(8)调用函数的时候,这意味着你不仅调用了signal函数,而且还调用了signal返回到指针指向到函数,最后(8)的结果肯定就是一个void了,所以假设语句(9)存在,那么肯定编译不能通过,因为my_signal(5,func)(6)到返回值是一个void,不可能由%X输出,而且也不能由其它格式化输出。
迷惑2:如果在my_signal函数到定义为
void (*my_signal(int sig,void (*handler)(int h)))(int c){
printf("in my_signal/n");
}
那么编译think.c能通过吗?因为没有返回值。think.c如果能够通过,那么生成到程序运行会有错吗?
1)think.c能够通过编译,因为在C语言中,函数到返回类型如果没有声明,那么就默认是int,如果没有返回(return)那么也默认是返回int,当然你也可以使用一个变量来捕捉函数返回到值,但是这个值通常是一个未定义的。
2)think.c编译生成的程序,运行到时候肯定会发生段错误,这个段错误也就是由于my_signal没有返回正确到函数指针导致的,因为没有明确返回一个合法到函数指针,所以my_signal运行后会返回一个为定义的值,而当运行到main中调用(7)的时候,显然,这里会发生段错误,因为本程序根本不能访问那段内存空间!
最后,在Linux Kernel中signal函数绑定信号处理函数后,确实会返回一个函数指针,它返回到是以前这个信号到处理函数指针,所以如果你存储了这个指针,那么当你处理完成以后就可以恢复以前的信号绑定。
更多推荐
所有评论(0)