Linux系统信号管理相关操作函数

来源:互联网 发布:淘宝企业店铺所需资料 编辑:程序博客网 时间:2024/05/17 04:28
Unix信号管理
    信号(signal)
    Unix系统中,用信号实现软件中断;
    Ctrl+C        ->SIGINT
    段错误        ->SIGSEGV
    总线错误    ->SIGBUS
    浮点数例外    ->SIGFPE
    终端关闭    ->SIGHUP
    定时到期    ->SIGALARM
    Ctrl+\        ->SIGQUIT不同于信号2,会产生core dump文件;

中断的概念
    中断(interrupt)是程序中止(暂停)现在的代码,转而执行其他的代码;
    中断分为软件中断和硬件中断;软件中断就是用软件的方式中断程序,硬件中断就是硬件出故障导致程序中断;
    软件中断的主要方式就是信号;
    信号的本质是非负整数;每个信号都有一个宏名称,都以SIG开头,比如信号2是SIGINT宏;
    用命令kill可以查看/发送信号;
    kill -9 pid    给进程发信号9,用于结束进程;
    kill -l        字母l(list),可以查看所有的信号;
    
关于0做除数
    整数除以0会引发浮点数例外FPE,终止程序;
    浮点除以0会得到结果,结果是无穷大;

    Unix信号和Linux信号不同,linux:0-64,中间不连续;Unix:0-48,此外,有些信号对应的值也不相同;因此编程时尽量使用信号的宏名称,而不要用数值;
    信号是异步处理方式;无法判断信号什么时候会来,因此就无法知道什么时候处理信号;
    信号0有特殊用途,本身不代表任何的事件,也不会处理;用于测试是否有权限发信号;
    kill -0 1
    进程之间可以互相发信号,但受到用户权限的影响;用户只能给自己的进程发信号,不能给其他用户的进程发信号;root可以给所有进程发信号;

    硬件故障和函数调用都可能产生信号;
信号的分类
    不可靠信号//这种信号不支持排队,因此有可能丢失,是非实时信号;
            //1-31都是不可靠信号;
    可靠信号//支持排队,因此不会丢失,是实时信号;
            //34-64都是可靠信号;
    
程序收到信号以后的处理方式
    1-> 默认处理方式,80%的默认处理方式是退出进程;
    2-> 忽略信号,不做任何处理;
    3-> 程序员可以自定义信号的处理方式,只需要写一个信号处理函数;
    信号9不能被忽略,也不能自定义;

信号处理的实现步骤
    1-> 写一个信号处理函数;
    2-> 用signal()/sigaction()注册信号处理方式;
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler函数指针);
    将信号与处理方式绑定在一起;
    第2个参数为函数指针 void (*fa)(int),函数为void类型,参数为int;
    函数返回函数指针格式,即返回函数;
    失败返回SIG_ERR;
当signal(信号, 函数指针)执行完毕后,只要有信号来,系统就会调用函数指针所对应的函数;
    第2个参数决定了信号的处理方式,可以是
        SIG_IGN        忽略;
        SIG_DFL        默认处理(一般都是恢复成默认);
        函数指针    自定义函数的处理方式;
/* * signal()函数演示 * 再用命令kill发信号9和11试试 */#include <stdio.h>#include <signal.h>void func(int signo) {        //signo就是信号    printf("捕获到信号%d\n", signo);    signal(SIGINT, SIG_DFL);    //恢复信号2默认处理方式    printf("信号2已恢复默认\n");}int main() {    signal(SIGINT, func);    //把信号2该成自定义func处理;    signal(SIGQUIT, SIG_IGN);    //把信号3改为忽略(Ctrl+\);    signal(SIGKILL, func);    //信号9只能默认,但不报错;    //signal(SIGINT, SIG_DFL);    printf("pid = %d\n", getpid());    while (1) ;    return 0;}

子进程的信号处理
    如果创建了子进程,
    fork()创建的子进程完全沿袭父进程的信号处理方式,
    vfork()+execl()创建的子进程,父进程默认子进程也默认,父进程忽略子进程也忽略,父进程自定义处理函数子进程默认;
    原因是vfork()+execl()创建的子进程代码区没有父进程的处理函数;
    忽略和默认的宏所有的进程都可以共享,所以能够继承;
/* * fork()与信号处理 */#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>void fa(int signo) {    printf("捕获了信号%d\n", signo);}int main() {    if (signal(SIGINT, fa) == SIG_ERR) {        //先设置再判断//信号2自定义        perror("signal 2"), exit(-1);    }    signal(SIGQUIT, SIG_IGN); //信号3忽略;    pid_t pid = fork();    if (pid == 0) {        /* 子进程执行分支 */        printf("chpid = %d\n", getpid());        while (1) ;        /* 分别用kill给子进程发信号验证子进程信号的处理方式 */        exit(0);    }    printf("father pid = %d\n", getpid());    printf("father over\n");    return 0;}


/* * vfork()+execl()信号处理方式 */#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>void fa(int signo) {    printf("捕获了信号%d\n", signo);}/* 练习写代码验证vfork()+execl()子进程对信号的处理方式 */int main() {    if (signal(SIGINT, fa) == SIG_ERR) {        /* 先设置再判断//信号2自定义 */        perror("signal 2"), exit(-1);    }    signal(SIGQUIT, SIG_IGN);    //信号3忽略;    pid_t pid = vfork();    if (pid == 0) {        /* 子进程执行分支 */        printf("chpid = %d\n", getpid());        /* 分别使用kill命令给子进程发信号验证处理方式 */        execl("./proc", "proc", NULL);        /*         * //proc.c         * #include <stdio.h>         * int main() {         *     printf("%d\n", getpid());         *     while (1) ;         *     return 0;         * }         */        //while(1);        exit(0);    }    printf("father over\n");    return 0;}

   
killall a.out    可以结束所有的a.out进程;

发信号的方法
    1.键盘发送信号(部分信号);
        Ctrl+C    发送信号2,SIGINT;
        Ctrl+\    发送信号3,SIG_QUIT;
    2.出错(部分信号);
        整数除以0    信号8,SIGFPE;
        段错误        信号11,SIGSEGV;
        总线错误    信号7,SIGBUS;
    3.kill命令发送(全部信号);格式:
        kill -信号 进程PID
    4.信号发送函数
        raise()        给本进程发任意信号;
        kill()        给任意进程(或进程组)发任意信号;
        alarm()        给本进程发特定信号(闹钟);
        sigqueue()    给任意进程发任意信号,可以附带额外的数据(少用);

    主讲kill()函数,了解alarm();
int kill(pid_t pid, int signo);
参数
    pid指定发给哪个/哪些进程;
    signo就是发送哪个信号;
pid有4种值
    正数    给进程ID=pid的特定进程发信号;
    -1        给所有有权限的进程发信号;
    0        给本进程组的进程发信号;
    <-1        给进程组ID=-pid的进程发信号;
/* * kill()函数练习 */#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>void fa(int signo) {    printf("进程%d捕获到了信号%d\n", getpid(), signo);}int main() {    pid_t pid = fork();    if (pid == 0) {        signal(SIGINT, fa);    //子进程注册信号处理方式        while (1) ;    }    printf("父进程%d给子进程发信号\n", getpid());    sleep(1);    kill(pid, SIGINT);    return 0;}


unsigned int alarm(unsigned int seconds);
    seconds秒之后产生一个闹钟信号,SIGALRM;不会产生第2个;
    如果seonds为0,将不会有新的闹钟被注册,意味着取消所有闹钟;
    如果之前已经注册过alarm()但还未到时,将被取消;
如果当前没有正在运行的闹钟,将返回0;
如果还有没结束的闹钟,将返回前面的闹钟剩余的秒数;
    可以用来做一些计划任务;
/* * alarm()函数使用 */#include <stdio.h>#include <unistd.h>#include <signal.h>void fa(int signo) {    printf("捕获了信号%d\n", signo);}int main() {    signal(SIGALRM, fa);    alarm(1);    sleep(2);    unsigned int res = alarm(5);    //如果前面没有正在运行的闹钟,返回0;    printf("res = %u\n", res);    sleep(3);    res = alarm(5);        //返回之前闹钟的剩余秒数;    printf("res = %u\n", res);    sleep(1);    res = alarm(5);        //返回之前闹钟的剩余秒数;    printf("res = %u\n", res);    while (1) ;    return 0;}


unsigned int sleep(unsigned int seconds);
    会使调用的线程休眠,直到seconds到时;或者被没有忽略的信号中断;
    可能休眠seconds秒;也可能被非忽略的信号打断,并返回剩余秒数;
    睡足seconds秒后返回0,被信号中断返回剩余秒数;

int usleep(useconds_t usec);
    usleep()是微秒(microsecond)级的休眠函数;
    成功返回0,失败返回-1;
microsecond微秒us
milisecond毫秒ms
/* * sleep()函数演示 */#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <signal.h>void fa(int signo) {    printf("捕获了信号%d\n", signo);}void print();int main() {    signal(SIGINT, fa);//为了不让信号退出进程,方便看到sleep效果    printf("begin\n");    unsigned int res = sleep(10); //睡足返回0,被信号打断返回剩余秒数;    /* sleep()会被未忽略的信号中断; */    printf("sleep() return value = %u\n", res); //返回剩余秒数    print();    printf("over\n");    return 0;}/* usleep()是微秒级的休眠;10的-6次方 */void print() {    int i = 0;    while (i++ <= 10) {        printf("%d ", i);        usleep(100000); //0.1s        fflush(NULL);    }    printf("\n");}


如果用一个int类型表示一个信号有无似乎有些浪费资源,可以考虑只用一个二进制位来表示信号的有无;
    long long int 是标c的64位整数;
    32位的有符号整数大致范围在正负21亿;即0x80000000到0x7FFFFFFF之间;
    32位的无符号整数大致范围在0到42亿;即0x00000000到0xFFFFFFFF之间;
做软件要考虑到扩展性,兼容性,健壮型,可移植性;
思考,如何存储一个50位的超大整数;
    方法1,以字符串形式存放;3 + '0' = '3';
    方法2,定义一个结构体,结构体里面分段存储;

信号集
    信号集就是信号的集合,可以理解成一个超大的整数(为了操作系统的扩展性);
    用一个二进制位对应一个信号(1代表有,0代表无);
    最后一位代表信号1,如果最后一位是1代表信号集中有信号1,如果是0,就没有信号1;
    信号集的类型    sigset_t;
    使用sizeof关键字可以获得sigset_t类型的大小,实际是128字节的超大整数;

对比数据结构
数据结构中逻辑结构包括
    1-> 集            元素之间没有任何关系,除了在一个集里面;
    2-> 线性结构    元素之间是前后一对一的关系;
    3-> 树形结构    元素之间是一对多的父子关系;
    4-> 网状结构    元素之间是多对多的网格关系;
物理结构
    线性和链表
运算结构
    1-> 创建和删除;
    2-> 增加元素和删除元素;
    3-> 修改和查找;
    4-> 排序和其他算法;

关于数据结构sigset_t,需要提供基本的函数如下
    1-> 增加元素
        sigaddset()        增加一个信号(将对应二进制位置1);
        sigfillset()    放入所有信号(全部增加);
    2-> 删除信号
        sigdelset()        删除一个信号(将对应二进制位置0);
        sigemptyset()    清空信号集(全部删除);
    3-> 查找信号
        sigismember()    判断信号是否存在信号集中;
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
    成功返回0,失败返回-1;
int sigismember(const sigset_t *set, int signum);
    存在返回1,不存在返回0,失败返回-1;
/* * 信号集练习 */#include <stdio.h>#include <signal.h>void print(const sigset_t *set, int size) {    int i = size;    char *p = (char *)set;    printf("0x");    while (i-- > 0) {        printf("%02x", p[i]);    }    printf("\n");}int main() {    printf("set size = %d\n", sizeof(sigset_t)); //128    /* sigset_t的大小是128字节,可以容纳128*8个信号; */    sigset_t set;    printf("清空前set: "); print(&set, sizeof(sigset_t));    /* 清空信号集 */    sigemptyset(&set);    //清空set    printf("清空后set: "); print(&set, sizeof(sigset_t));    /* 增加信号 */    sigaddset(&set, 1); //第1个bit置1    printf("放入1, set: "); print(&set, sizeof(sigset_t));    sigaddset(&set, 2);    printf("放入2, set: "); print(&set, sizeof(sigset_t));    sigaddset(&set, 3); //第3个bit置1    printf("放入3, set: "); print(&set, sizeof(sigset_t));    sigaddset(&set, 7);    /* printf("放入7, set = %d\n", set); */    printf("放入7, set: "); print(&set, sizeof(sigset_t));    /* 删除信号 */    sigdelset(&set, 3); //第3个bit清0    /* printf("删除3, set = %d\n", set); */    printf("删除3, set: "); print(&set, sizeof(sigset_t));    /* 查找信号 */    if (sigismember(&set, 2)) {        printf("信号2在信号集中\n");    } else {        printf("信号2不在信号集中\n");    }    return 0;}


信号屏蔽
    在执行一些关键代码时,进程需要屏蔽一些信号,防止信号中断关键任务;
    信号屏蔽无法保证信号不来,但信号可以暂时不做处理,不会中断关键代码;等到关键代码执行完毕,解除信号屏蔽时再中断代码,返回执行信号处理的函数;
    函数sigprocmask()负责信号屏蔽和解除信号屏蔽;
int sigprocmask(int how, const sigset_t *newset, sigset_t *oldset);
参数
    newset    代表需要屏蔽的信号(集);//传入的参数
    oldset    代表传出旧的信号屏蔽字(不用传出给NULL);//传出的参数;
    how是运算的方式
        SIG_BLOCK    旧的+新的,比如
            abc + cde -> abcde
        SIG_UNBLOCK    旧的-新的,比如
            abc - cde -> ab
        SIG_SETMASK    直接使用新的,不考虑旧的,比如
            abc  cde -> cde
        注:how直接选择SIG_SETMASK即可;

    sigset_t pend;
    sigpending(&pend);可以取屏蔽期间哪些信号来过;
int sigpending(sigset_t *set);
    可以将屏蔽期间捕获的信号存入set中;
    成功返回0,失败返回-1;
/* * sigprocmask()信号屏蔽练习 */#include <stdio.h>#include <stdlib.h>#include <signal.h>void fa(int signo) {    printf("捕获了信号%d\n", signo);}int main() {    signal(SIGINT, fa);    //不可靠信号不支持排队,屏蔽时丢失;    signal(SIGQUIT, fa); //只能知道该信号来过;    signal(50, fa);        //可靠信号支持排队,屏蔽时不丢失;    printf("pid = %d\n", getpid());    printf("执行普通程序,不屏蔽信号\n");    sleep(20);    printf("++++++++++++++++++++++++\n");    printf("执行关键程序,屏蔽信号\n");    sigset_t new, old;    sigemptyset(&new);    sigaddset(&new, SIGINT);    sigaddset(&new, SIGQUIT);    sigaddset(&new, 50); //已准备号待屏蔽的信号    sigprocmask(SIG_SETMASK, &new, &old); //屏蔽    sleep(15);    printf("关键代码执行完毕,将解除屏蔽\n");    sigset_t pend;    sigpending(&pend); //解除屏蔽前取信号屏蔽期间哪些信号来过;    printf("------------------------\n");    sigprocmask(SIG_SETMASK, &old, NULL); //解除屏蔽    printf("已解除屏蔽\n");    int i = 0;    for (i = 1; i <= 63; i++) {        /* if (sigismember(&pend, SIGINT)) { */        if (sigismember(&pend, i)) {            printf("信号屏蔽期间,信号%d来过\n", i);        }    }    sleep(10);    return 0;}



0 0
原创粉丝点击