Linux环境:C编程之信号机制

    xiaoxiao2022-07-07  198

    Linux环境:C编程之信号机制

    信号概述什么是信号信号的来源进程对信号的处理常用信号 signal 信号处理机制signal函数示例程序:信号捕捉和打断特殊情形处理 sigaction信号处理机制sigaction函数:注册信号处理函数struct sigaction结构体:信号处理信息示例程序:信号打断重入示例程序:信号处理不可打断sigprocmask 信号阻塞kill函数 计时器与信号睡眠函数计时器计时器种类计时器设置函数计时器原理

    信号概述

    什么是信号

    信号是进程在运行过程中,由自身产生或由进程外部发过来的消息,用整型常量表示,在头文件<signal.h>中定义了一系列宏表示不同的信号。

    信号的来源

    信号由内核产生,内核产生信号的情形有三种:

    用户:用户能够通过输入 CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;进程:一个进程可以通过系统调用 kill 给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信
    进程对信号的处理

    进程对信号的处理方法有三种:

    接收默认处理:进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行;忽略信号:进程可以通过代码,显示地忽略某个信号的处理捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理 注意: 有两个信号既不能被忽略也不能被捕捉,它们是 SIGKILL 和 SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止进程。SIGSTOP 是暂停进程。 SIGKILL 是杀死进程。
    常用信号

    常用的信号如下:

    信号值默认处理说明SIGHUP1A在控制终端上是挂起信号, 或者控制进程结束SIGINT2A从键盘输入的中断SIGQUIT3C从键盘输入的退出SIGILL4C无效硬件指令SIGABRT6C非正常终止SIGFPE8C浮点运算例外SIGKILL9AEF杀死进程信号SIGSEGV11C无效的内存引用SIGPIPE13A管道中止: 写入无人读取的管道SIGALRM14A来自 alarm(2) 的超时信号SIGTERM15A终止信号SIGUSR130,10,16A用户定义的信号 1SIGUSR231,12,17A用户定义的信号 2SIGCHLD20,17,18B子进程结束或停止SIGCONT19,18,25继续停止的进程SIGSTOP17,19,23DEF停止进程SIGTSTP18,20,24D终端上发出的停止信号SIGTTIN21,21,26D后台进程试图从控制终端(输入SIGTTOU22,22,27D后台进程试图在控制终端输出

    默认处理字母代表的含义:

    A 缺省动作是结束进程. B 缺省动作是忽略这个信号. C 缺省动作是结束进程, 并且核心转储. D 缺省动作是停止进程. E 信号不能被捕获. F 信号不能被忽略.

    signal 信号处理机制

    signal函数

    signal函数原型: sighandler_t signal(int signum, sighandler_t handler);

    handler是信号处理函数,负责处理信号。sighandler_t为自定义类型,是一个函数指针,指向一个返回值为void,参数为一个int的函数。通过该类型可以定义信号处理函数,对传入的信号进行处理。可以是系统默认的信号处理函数SIG_DFL(表示交由系统缺省处理,相当于白注册了)或 SIG_IGN(表示忽略掉该信号而不做任何处理)signum代表某种类型的信号。signal函数注册成功返回该信号以前处理函数的地址,失败返回SIG_ERR,即-1的强制类型转换。函数原理:通过signal函数可以将特定类型的信号与一个信号处理函数联系起来,称为信号处理函数的注册。注册之后进程收到的该类型的信号将全部交给对应的信号处理函数处理。一个信号处理函数可以对多种信号进行处理,只需要用signal多次注册就可以了。从而实现了对信号的捕捉处理。
    示例程序:信号捕捉和打断
    void sighandle1(int sig) { printf("信号处理%d:打印0-9\n",sig); for(int i=0;i<10;i++) { printf("%d\n",i); sleep(1); } } void sighandle2(int sig) { printf("信号处理%d:\n中断\n",sig); int i = 0; while(i++<10) { sleep(1); printf("!\n"); } } int main(int args,char *argv[]) { signal(2,sighandle1); signal(3,sighandle2); while(1); return 0; }

    执行效果: 注意,当接收某个信号的信号处理函数被打断时,对后续到来的该信号不再响应。 即同一时刻只能有一个函数对该类型信号进行处理。

    特殊情形处理

    1、 注册一个信号处理函数,并且处理完毕一个信号之后,不需要重新注册,就能够捕捉下一个信号 2、 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,会在处理完当前信号后处理下一个信号,后续相同信号忽略(会多执行一次)。 3、 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时会跳转去执行另一个信号,之后再执行剩下的没有处理完的信号 4、 如果程序阻塞在一个系统调用(如 read(…))时,发生了一个信号,这时会先跳转到信号处理函数,等信号处理完毕后,系统调用再返回

    sigaction信号处理机制

    sigaction函数:注册信号处理函数

    函数原型: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) 参数和返回值:

    参数 signum 为需要捕捉的信号参数act是一个结构体,里面包含信号处理函数地址、处理方式等信息参数 oldact 是一个传出参数,sigaction函数调用成功后,oldact 里面包含以前对signum的处理方式的信息,通常为 NULL如果函数调用成功,将返回 0,否则返回-1
    struct sigaction结构体:信号处理信息

    结构体定义:

    struct sigaction { void (*sa_handler)(int); //signal机制下的信号处理函数指针 void (*sa_sigaction)(int, siginfo_t *, void *);//sigaction机制下的信号处理函数指针 sigset_t sa_mask; //将要被阻塞的信号集合 int sa_flags; //信号处理方式掩码 void (*sa_restorer)(void); //保留,不使用 }; 字段 sa_handler是一个函数指针,用于指向原型为 void handler(int)的信号处理函数地址,即signal机制下的信号处理函数字段 sa_sigaction 也是一个函数指针,用于指向原型为:void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved);的信号处理函数,即sigaction机制下的信号处理函数。该函数的三个参数含义为:iSignNum传入的信号 pSignInfo为信号携带的数据信息, 捕捉到信号时同时传入,结构体的定义如下;pReserved为保留的进程上下文信息。 struct siginfo_t { int si_signo; /* 信号值,对所有信号有意义*/ int si_errno; /* errno值,对所有信号有意义*/ int si_code; /* 信号产生的原因,对所有信号有意义*/ pid_t si_pid; /* 发送信号的进程ID,对kill(2),实时信号以及SIGCHLD有意义 */ uid_t si_uid; /* 发送信号进程的真实用户ID,对kill(2),实时信号以及SIGCHLD有意义 */ int si_status; /* 退出状态,对SIGCHLD有意义*/ clock_t si_utime; /* 用户消耗的时间,对SIGCHLD有意义 */ clock_t si_stime; /* 内核消耗的时间,对SIGCHLD有意义 */ sigval_t si_value; /* 信号值,对所有实时有意义,是一个联合数据结构,可以为一个整数(由si_int标示,也可以为一个指针,由si_ptr标示)*/ void * si_addr; /* 触发fault的内存地址,对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/ int si_band; /* 对SIGPOLL信号有意义 */ int si_fd; /* 对SIGPOLL信号有意义 */ } 字段 sa_mask 是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对 sigset_t 结构体,有一组专门的函数对它进行处理: int sigemptyset(sigset_t *set); //清空信号集合 set, 成功返回 0,失败返回-1.int sigfillset(sigset_t *set); //将所有信号填充进 set 中,成功返回 0,失败返回-1.int sigaddset(sigset_t *set, int signum); //往 set 中添加信号 signum,成功返回 0,失败返回-1.int sigdelset(sigset_t *set, int signum); //从 set 中移除信号 signum,成功返回 0,失败返回-1.int sigismember(const sigset_t *set, int signum); //判断 signum 是否包含在 set 中(是:返回 1,否:0),成功返回 0,失败返回-1.int sigpending(sigset_t *set); //将被阻塞的信号集合由参数 set 指针返回(挂起信号),成功返回 0,失败返回-1. 字段 sa_flags 是一组掩码的合成值,指示信号处理时所应该采取的一些行为,当为0时表示启用signal机制的信号处理函数sa_handler 掩码描述SA_RESETHAND处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号SA_NODEFER在处理信号时,收到其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,不断重入,次数不丢失SA_RESTART如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,返回阻塞的系统调用处。如果不指定该参数,中断处理完毕之后,read 函数读取失败SA_SIGINFO指示结构体的信号处理函数指针是哪个有效,如果 sa_flags 包含该掩码,则 sa_sigaction 指针有效,否则是 sa_handler 指针有效
    示例程序:信号打断重入
    #include <fun.h> void sighandle1(int sig,siginfo_t *pinfo,void *p) { printf("信号处理%d:打印0-9\n",sig); for(int i=0;i<10;i++) { printf("%d\n",i); sleep(1); } printf("信号%d处理结束!\n",sig); } void sighandle2(int sig,siginfo_t *pinfo,void *p) { printf("信号处理%d:\n中断\n",sig); int i = 0; while(i++<5) { sleep(1); printf("!\n"); } printf("信号%d处理结束!\n",sig); } int main(int args,char *argv[]) { struct sigaction sigm; sigm.sa_sigaction = sighandle1; sigm.sa_flags = SA_NODEFER|SA_SIGINFO; sigaction(2,&sigm,NULL); sigm.sa_sigaction = sighandle2; sigaction(3,&sigm,NULL); while(1); return 0; }
    示例程序:信号处理不可打断
    #include <fun.h> struct sigaction sigm; void sighandle1(int sig,siginfo_t *pinfo,void *p) { printf("信号处理%d:打印0-5\n",sig); sigset_t pending; for(int i=0;i< 6;i++) { printf("%d\n",i); sleep(1); sigpending(&pending); if(sigismember(&pending,3)) { printf("信号3已被阻塞!\n"); } } printf("信号%d处理结束!\n",sig); } void sighandle2(int sig,siginfo_t *pinfo,void *p) { printf("信号处理%d:\n中断\n",sig); int i = 0; while(i++<5) { sleep(1); printf("!\n"); } printf("信号%d处理结束!\n",sig); } int main(int args,char *argv[]) { sigemptyset(&sigm.sa_mask); sigaddset(&sigm.sa_mask,3); sigm.sa_sigaction = sighandle1; sigm.sa_flags = SA_NODEFER|SA_SIGINFO; sigaction(2,&sigm,NULL); sigm.sa_sigaction = sighandle2; sigaction(3,&sigm,NULL); while(1); return 0; }
    sigprocmask 信号阻塞

    与struct sigaction中设置的被被阻塞信号集合只在处理信号时阻塞不同,sigprocmask函数可以使信号全程被阻塞,除非再次用sigprocmask 设置阻塞信号集合。

    函数原型:

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)

    参数:

    参数 how 的值为如下 3 者之一: a:SIG_BLOCK ,将参数 2 的信号集合添加到进程原有的阻塞信号集合中 b:SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数 2 中包含的信号 c:SIG_SETMASK,重新设置进程的阻塞信号集为参数 2 的信号集参数 set 为阻塞信号集参数 oldset 是传出参数,存放进程原有的信号集,通常为 NULL
    kill函数

    利用kill函数可以实现进程间互发信号 函数原型:

    int kill(pid_t pid, int sig);

    参数和返回值:

    参数 pid 为将要接受信号的进程的 pid,可以通过 getpid()函数获得来给自身发送信号,还可以发送信号给指定的进程,此时 pid 有如下描述: pid > 0 将信号发给 ID 为 pid 的进程pid == 0 将信号发送给与发送进程属于同一个进程组的所有进程pid < 0 将信号发送给进程组 ID 等于 pid 绝对值的所有进程pid == -1 将信号发送给该进程有权限发送的系统里的所有进程 参数 sig 为要发送的信号成功,返回 0,否则为-1

    计时器与信号

    睡眠函数
    unsigned int sleep(unsigned int seconds);//实现秒级睡眠 void usleep(unsigned long usec);//实现微秒级别的睡眠

    睡眠函数内部通过信号机制实现,先设定若干秒后产生唤醒信号,然后挂起自己。

    signal(SIGALRM,SignHandler); alarm(3); //等待 3 秒之后自动产生 SIGALRM 信号 pause(); //将进程挂起,直到有信号发生才退出挂起
    计时器
    计时器种类
    真实计时器,ITIMER_REAL,计算程序运行的实际时间,计时结束发送SIGALRM虚拟计时器,ITIMER_VIRTUAL,计算程序运行在用户态时所消耗的时间(实际时间减掉系统调用和程序睡眠所消耗的时间),计时结束发送SIGVTALRM实用计时器,ITIMER_PROF,计算的是程序处于用户态和处于内核态所消耗的时间之和,计时结束发送SIGPROF
    计时器设置函数

    int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); //设置计时器

    参数which代表计时器

    参数value代表新的计时器设置,传入参数

    参数ovalue代表旧的计时器设置,不用可以设为NULL,传出参数

    itimerval结构体定义如下:

    struct itimerval{ struct timerval it_interval;//重复时间间隔 struct timerval it_value;//初始时间间隔 }; struct timerval{ long tv_sec;//时间的秒数部分 long tv_usec;//时间的微秒部分 };

    int getitimer(int which, struct itimerval *value); //获取计时器的设置

    ``

    参数 which 指定哪个计时器,可选项为 ITIMER_REAL(真实计时器)ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器))参数 value 为一结构体的传出参数,用于传出该计时器的初始间隔时间和重复间隔时间返回值:如果成功,返回 0,否则-1
    计时器原理

    从初始间隔开始倒计时,倒计时结束发送对应的信号,然后把初始间隔设为重复间隔的值,重新开始倒计时,倒计时结束后发送信号。

    如果重复间隔的值为0,则只发送一次信号,相当于起到了延时发送信号的作用。

    如果初始间隔的值为0,则不会进行计时。

    最新回复(0)