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

chapeter 20 :信号的基本概念

内核信号机制实现

http://www.spongeliu.com/165.html
当进程P2向p1发送信号后,内核接受到信号,并将其放在p1的信号队列中,当p1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数。
- p1什么时候会陷入内核态?

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

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

概念

等待状态(pending):信号在产生后,会于稍后传递给某一进程,在产生和到达期间,就被称之为等待状态(pending)。如将信号添加到进程的信号掩码中,就会阻塞信号的到达,信号将处于等待状态。
信号处理器程序:用于为响应传递来的信号而执行适当的任务,可以通过signal等函数进行设置

常见linux信号

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

名称 信号值 描述 SUSv3 默认
SIGINT 2 终端中断 yes term

signal()

更改信号处理函数

#include
void (*signal(int sig, void (*handler)(int))) (int);

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

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

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

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

使用样例

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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

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

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

raise以及kill()向自身发送信号的时候,信号立即传递(因为发送信号的本质是通过调用系统内核进行信号发送,所以进程会马上处于内核态
raise出错将返回非0值(不一定是-1)。唯一可能发生的错误是sig无效 errno 变为EINVAL

strsignal(int sig)

显示信号的描述

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

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

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

#include
#include
#include
#include
#include
#include
#include
#include

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
int sigemptyset(sogset_t * set);//将信号集set中的所有信号清空
int sigfillset(sigset_t *set);//将信号集set中的添加所有信号
  • 添加删除函数
#include
int sigaddset(sigset_t *set,int sig);//向set中添加信号sig
int sigdelset(sigset_t *set,int sig);//向set中移除信号sig
  • 判断是否包含成员
#include
int sigismember(const sigset_t * set,int sig);判断sig是否为set成员,如果是返回1否则0

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

#include
#include
#include
#include
#include
#include
#include
#include

void print_sig_set(const sigset_t *set){
    for(int i=1;iif(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
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
int sigpending(sigset_t* set);成功返回0,失败-1

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

sigaction()

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

#include
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
int pause(void)l

你可能感兴趣的:(随笔,linux)