信号:
#include
kill -l :查看系统定义的信号列表。其中,1-31 普通信号(可能丢失),34-64 实时信号(不会丢失) --- 关注 当前信号有无产生
[1] SIGHUP : 中止信号
[2] SIGINT : 终止进程 Ctrl +c(前台终止) Ctrl+\
[3] SIGQUIT : 终止进程 并 Core Dump
[9] SIGKILL : 杀掉进程
[18] SIGCONT : 继续进程
[19] SIGSTOP : 停止进程
用户执行信号处理动作,称为 捕捉 该信号(自定义 该信号)。
Core Dump 可以使用gdb进行调试 查找 错误源。
实际执行信号的处理动作:忽略,执行默认动作,自定义动作。
一、产生信号:
1、终端按键 --- 只能发给前台进程
Ctrl+c Ctrl+\
2、调用系统函数
kill 函数可以给一个指定的进程发送指定的信号。
raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。
#include
#include
int kill(pid_t pid, int sig);
kill() : _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
int raise(int signo);
返回值:成功返回0,错误返回-1。
abort函数 使当前进程接收到SIGABRT信号而异常终止(保证成功) 。
#include
void abort(void);
就像 exit 函数一样, abort 函数总是会成功的,所以 没有返回值。
3、软件条件产生
#include
unsigned int alarm(unsigned int seconds); //预设闹钟,seconds后终止进程
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发 [14]SIGALRM 信号, 该信号的默认处理动作是终止当前进程。
返回值: 0 或者 以前设定的闹钟时间还余下的秒数。
seconds=0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
#include#include #include int main() { int count=6; alarm(1); while(1) { printf("%d\n",count); count++; } return 0; }
运行结果:
(1)alarm(1):设置闹钟,1s后终止进程
(2)alarm(0):相当于取消以前设定的闹钟
二、阻塞信号:
1、内核下信号表示
信号递达(Delivery) :实际执行信号的处理动作 (忽略,执行默认动作,自定义动作) 。
常见 信号递达 方式(3种):忽略,执行默认动作,自定义动作(捕捉信号)。
信号未决(Pending) :信号从产生到递达之间的状态 。
进程可以选择阻塞(Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才 执行递达的动作。
注意:信号被 阻塞就不会递达,而 忽略是在递达之后可选的一种处理动作。
每个信号 都有两个标志位分别表示 阻塞(block)和 未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达 才清除该标志。
PCB
block(信号阻塞):是否阻塞(状态) // sigset_t block;
pending(信号未决):记录当前信号是否产生(有无) // sigset_t pending;
handler:函数指针 表示处理动作
SIG_DFL:缺省 终止进程
SIG_IGN:忽略 ignore
一个信号,没有 pending,就不 block。
未决和阻塞标志可以用相同的数据类型 sigset_t来存储,sigset_t称为信号集,
阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为 阻塞 而不是忽略。
2、信号集操作函数
#include
int sigemptyset(sigset_t *set); //初始化set指向的信号集,使对应bit 清零
int sigfillset(sigset_t *set); //初始化set指向的信号集,使对应bit 置位
int sigaddset(sigset_t *set, int signo); //添加有效信号
int sigdelset(sigset_t *set, int signo); //删除有效信号
返回值:成功返回 0,出错返回 -1 。
int sigismember(const sigset_t *set, int signo); //判断信号是否在信号集中
返回值:若包含则返回 1, 不包含则返回 0, 出错返回 -1。
在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
3、sigprocmask // 读取或更改进程的信号屏蔽字(阻塞信号集)
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1。
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,则:
4、sigpending
#include
int sigpending(sigset_t *set);
sigpending 读取 当前进程的未决信号集,通过set参数传出。
返回值:调用成功则返回0,出错则返回-1。
#include#include void show_pending(sigset_t *sig_list) { if(sig_list) { int i=0; while(i++ < 32) { if(sigismember(sig_list,i)) { printf("1"); } else { printf("0"); } if(i%8 == 0) { printf(" "); } } } printf("\n"); } void handler(int sig) { printf("catched you!!! %d\n",sig); } int main() { sigset_t sig_set; sigset_t sig_set_old; sigset_t sig_pending; sigemptyset(&sig_set); //init *set 0 sigemptyset(&sig_set_old); sigemptyset(&sig_pending); sigaddset(&sig_set,SIGINT); //add valid signal sigprocmask(SIG_SETMASK,&sig_set,&sig_set_old); //read blocking signal(2,handler); int done=0,count=6; while(!done) { sigpending(&sig_pending); //read pending show_pending(&sig_pending); sleep(1); count--; if(count < 0) { sigprocmask(SIG_SETMASK,&sig_set_old,NULL); //reback blocking_old } } return 0; }
运行结果:
阻塞是一种状态,pending是信号的有无,handler表示处理动作。没有pending,就不能blocking。
三、捕捉信号:
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为 捕捉信号。
信号的处理过程(自定义动作):
地址空间 页表 内核地址空间 【0-3G用户使用】
内核模式:PSW寄存器(处理器状态字)2bit描述当前模式: ----内核页表
03Linux 05Unix ---- 用户模式(非零)---- 自定义页表
1、内核如何实现信号的捕捉
由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:
1)用户程序注册了SIGQUIT信号的处理函数sighandler。
2)当前正在执行main函数,这时发生中断或异常切换到内核态。
3)在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4)内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。
5)sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
6)如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
2、 sigaction // 读取和修改与指定信号相关联的处理动作
#include
int sigaction(int signo, const struct sigaction *act, struct
sigaction *oact); //signo:指定信号的编号
调用成功则返回 0,出错则返回 -1。
#include#include #include #include void handler(int sig) { printf("I am coming,that signal: %d\n",sig);//sigaction } int main()//sigaction { struct sigaction act; struct sigaction old; act.sa_handler=handler; act.sa_flags=0; sigemptyset(&act.sa_mask); memset(&old,'\0',sizeof(old)); signal(2,handler); sigaction(SIGINT,&act,&old); while(1) { printf("I am wating a signal...\n"); sleep(1); } return 0; }
运行结果:
3、 pause //使调用进程挂起直到有信号递达
#include
int pause(void);
如果信号的处理动作是 终止进程,则 进程终止, pause函数没有机会返回;
如果信号的处理动作是 忽略,则 进程继续处于挂起状态, pause不返回;
如果信号的处理动作是 捕捉,则 调用了信号处理函数之后 pause返回-1,errno设置为EINTR("被信号中断"), 所以pause只有出错的返回值.
没有返回值,若返回 则 出错:exec(程序替换) pause(进程挂起)
//利用 pause() 和 alarm(_time) 模拟 sleep(seconds)
#include#include #include #include void handler(int sig) { // DO Nothing //my_sleep } int my_sleep(int _time) { struct sigaction act; struct sigaction old; act.sa_handler=handler; act.sa_flags=0; sigemptyset(&act.sa_mask); sigaction(SIGALRM,&act,&old); alarm(_time); pause(); int ret=alarm(0); sigaction(SIGALRM,&old,NULL); return ret; } int main() { int i=0; while(1) { my_sleep(1); printf("1s passed:%d\n",i++); } return 0; }
运行结果: