您的位置:首页 > 运维架构 > Linux

linux系统编程手册阅读笔记-c20:信号的基本概念

2017-09-20 17:03 399 查看

chapeter 20 :信号的基本概念

内核信号机制实现

http://www.spongeliu.com/165.html

当进程P2向p1发送信号后,内核接受到信号,并将其放在p1的信号队列中,当p1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数。

- p1什么时候会陷入内核态?

当前进程由于系统调用、中断或异常而进入系统空间,到返回用户空间的前夕。

当前进程在内核中进入睡眠以后刚被唤醒的时候(必定是在系统调用中),或者由于不可忽略信号的存在而提前返回到用户空间

- 信号处理

当进程由于中断等陷入内核态之后,寄存器等信息会被压入内核栈,保存现场用于返回到用户栈时继续执行程序,在完成了系统调用后,会调用do_signal()函数(栈会被压入相关的信息),检查信号队列,如果有信号,则会根据信号向量表找到信号处理函数入口位置,将保存在内核栈上的eip指令寄存器修改至该入口,然后内核栈上的相应内容拷贝到用户栈上,跳转到用户态执行信号处理函数,执行完毕之后,根据(跳转时)用户堆栈记录的信息返回到内核态上,继续检查信号队列,反复这个过程,直到所有信号处理完毕。

概念

等待状态(pending):信号在产生后,会于稍后传递给某一进程,在产生和到达期间,就被称之为等待状态(pending)。如将信号添加到进程的信号掩码中,就会阻塞信号的到达,信号将处于等待状态。

信号处理器程序:用于为响应传递来的信号而执行适当的任务,可以通过signal等函数进行设置

常见linux信号

信号在linux中以数字进行编号,为了更方便使用,linux也为之起了变量别名

名称信号值描述SUSv3默认
SIGINT2终端中断yesterm

signal()

更改信号处理函数

#include<signal.h>
void (*signal(int sig, void (*handler)(int))) (int);

更改sig的信号处置函数为handler,返回旧的信号处理函数指针,如果失败将返回SIG_ERR(一个函数指针)


除了自定义的handler信号处理函数之外,还提供重置和忽略函数

void handler(int sig){ //自定义
/*-----*/
}

SIG_DFL //重置默认处理函数
SIG_IGN //忽略信号,内核会直接丢弃该信号


使用样例

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>
#include<typeinfo>
#include<iostream>
#include<cxxabi.h>
using namespace std;

void errExit(const char * str){
fprintf( stderr,"%s\n",str);
exit(-1);
}

void handler(int sig){
printf("caputre ternimal interrupt\n");
}

int main(int argc, char*argv[]){
void (*old_handler)(int);
old_handler= signal(SIGINT,handler);   //为SIGINT绑定新的信号处理函数。

if(old_handler == SIG_ERR)          //如果更改信号处理函数失败
errExit("signal err");

sleep(10);
/*在此时按下contrl + c*/
sleep(10);

if(signal(SIGINT,old_handler)==SIG_ERR) //恢复旧的信号处理函数
errExit("signal err");
return 0;
}

>>>>>>>>>>>>>>>>>>>>>>>>>要使用g++进行编译

woder@ubuntu:~/project/osprogram$ ./test
^Ccaputre ternimal interrupt
^Ccaputre ternimal interrupt


上文程序一个需要注意的地方就是使用了两次sleep(),如果只使用了一个sleep(),本来处于睡眠状态的进程会因为信号的到来被唤醒,进行信号处理然后顺序执行结束进程,让人误以为执行了新的处理器函数后,还执行了原来的中断处理函数,其实没有。

kill()

发送信号函数

#include<signal.h>

int kill(pid_t pid, int sig);   向进程pid发送信号sig,成功返回0,失败返回-1


pid不同情况的含义

pid > 0: 发送给pid进程

pid = 0: 发送给同组进程的每个进程,包括自己

pid <-1: 发型到每个有权利可以发送的进程,除了init(pid=1)和自身进程

发送信号的权限

1.特权级,可以向所有进程发送任何信号

2.root用户和init进程是特例,只能接受已经安装了处理器函数的信号,防止init被杀死

3.发送进程的实际用户id或有效用户id等于接受进程的实际用户id或保存设置用户id。

如果无权发送的话,kill()会返回-1,并将errno置为EPERM (发送组的时候成功一个就算成功)。

raise()

向自身发送信号

#include<signal.h>

int raise(int sig);给自身发送sig信号


raise以及kill()向自身发送信号的时候,信号立即传递(因为发送信号的本质是通过调用系统内核进行信号发送,所以进程会马上处于内核态

raise出错将返回非0值(不一定是-1)。唯一可能发生的错误是sig无效 errno 变为EINVAL

strsignal(int sig)

显示信号的描述

#include<signal.h>
extern const char * const sys_siglist[];//存储着相应信号描述的数组

char *strsignal(int sig);//返回对应的信号描述字符串的地址


下列使用两种方式获取关于 contrl+c即终端中断信号的描述

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>

extern const char * const sys_siglist[];

int main(int argc, char*argv[]){
char *sig_desc=strsignal(SIGINT);
printf("%s\n",sig_desc);
printf("%s\n",sys_siglist[SIGINT]); //两种结果都一样
return 0;
}

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>使用g++
woder@ubuntu:~/project/osprogram$ ./test
Interrupt
Interrupt


信号集

信号集存储着一组不同的信号,是一种数据结构,数据类型为
sigset_t


初始化函数

#include<signal.h>
int sigemptyset(sogset_t * set);//将信号集set中的所有信号清空
int sigfillset(sigset_t *set);//将信号集set中的添加所有信号


添加删除函数

#include<signal.h>
int sigaddset(sigset_t *set,int sig);//向set中添加信号sig
int sigdelset(sigset_t *set,int sig);//向set中移除信号sig


判断是否包含成员

#include<signal.h>
int sigismember(const sigset_t * set,int sig);判断sig是否为set成员,如果是返回1否则0。


可以通过此函数对信号1~NSIG-1(信号数量)进行一个遍历,判断是否为当前信号集中的信号

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>

void print_sig_set(const sigset_t *set){
for(int i=1;i<NSIG;i++){
if(sigismember(set,i)){
printf("signal %d in set\n",i);
}
}
}

int main(int argc, char*argv[]){
sigset_t set;
sigfillset(&set);
print_sig_set(&set);
return 0;
}


信号掩码

每个进程都有一个信号掩码,即一个用来阻塞部分信号对于该进程的传递的信号集,如果被阻塞的信号发送给了进程,该信号会进入进程的等待队列,直到掩码不再对该型号进行阻塞,进程才从等待信号集中获取该信号,引发处理器函数的信号会被自动的添加到等待信号集中,等处理器函数处理完毕后,才对该信号接触阻塞

sigprocmask

显示对向当前进程的信号掩码中添加,或移除信号

#include<signal.h>
int sigprocmask (int how, const sigset_t *set,sigset_t *oldset);成功返回0,失败返回-1


对于how有三个参数描述如下

SIG_BLOCK:将信号集set中的信号添加到信号掩码中

SIG_UNBLOCK:将信号集set中的信号从信号掩码中移除

SIG_SETMASK:将信号集set中的信号直接赋值给信号掩码

oldset是返回的之前的信号掩码

sigpending()

获取当前处于等待队列中的信号

#include<signal.h>
int sigpending(sigset_t* set);成功返回0,失败-1


同一信号记录在等待信号集中,阻塞多次的情况下,在解除阻塞后只传递一次

sigaction()

除了signal()可以指定信号处理函数之外,sigaction()也可以

#include<signal.h>
int sigaction(int sig,const struct sigaction * act, struct sigaction * oldact);成功返回0,失败-1

sigaction结构如下
struct sigaction{
void (*sa_handler)(int);    //对应于signal的handler,信号处理函数的地址
sigset sa_mask;             //信号掩码

int sa_flags;
void (*sa_restorer)(void);
};


pause

调用pause将暂停进程的执行,直至信号处理器函数终端该调用

#include<unistd.h>
int pause(void)l
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: