Linux多进程开发III
1.信号,事件发生时对进程的通知机制,有时称为软件中断,是在软件层次上对中断机制的一种模拟,是一种异步通信方式。发送进程的信号一般来源于内核。
引发内核为进程产生信号的事件:
-
对于前台进程,用户可以通过键盘输入特殊的中断字符来给它发送信号。
-
硬件发生异常,即硬件检测到一个错误条件并通知内核,再由内核发送相应信号给相关进程。
-
系统状态变化,如alarm定时器到期,进程执行的CPU时间超限等。
-
运行kill命令或调用kill函数。
2.使用信号的目的:
-
让进程知道发生了特定的事情。
-
强迫进程执行它自己代码中的信号处理程序。
3.信号的特点:
-
简单
-
不能携带大量信息
-
满足特定条件才发送
-
优先级比较高
4.查看系统定义的信号列表。前31为常规信号,其余为实时信号。
kill -l
5.常用信号。
SIGIN <Ctrl + C>组合键,用户终端向正在运行的由此终端启动的程序发送信号 终止进程 SIGQUIT <Ctrl + \>组合键,用户终端向正在运行的由此终端启动的程序发送信号 终止进程 SIGKILL 无条件终止进程,不能被忽略,处理和阻塞 终止进程,可以杀死任何进程 SIGSEGV 知识进程进行了无效内存访问(段错误) 终止进程并产生core文件 SIGPIPE 破裂管道,向一个没有读端的管道写数据 终止进程 SIGCHILD 子进程结束时,父进程会收到该信号 忽略该信号 SIGCONT 如果进程已停止,则使其继续运行 继续/忽略 SIGSTOP 停止进程的执行。信号不能被忽略,处理和阻塞 终止进程
6.信号的5中默认处理动作。
-
Term 终止进程
-
Ign 当前进程忽略该信号
-
Core 终止进程并生成一个core文件
-
stop 暂停当前进程
-
Cont 继续执行当前被暂停的进程
7.信号的几种状态。
-
产生
-
未决
-
递达
8.SIGKILL和SIGSTOP信号不能被捕捉、阻塞和忽略。只能执行默认的动作。
9.生成core文件并查看。
ulimit -a // 查看core文件大小 ulimit -c 1024 // 修改core文件大小 (gdb) core-file core // 在gdb调试下查看core文件中的错误
10.信号相关的函数。
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); - 作用:给某个进程pid发送某个信号 - pid: 需要发送信号的进程,>0 发送给指定进程;=0 发送给当前进程组;=-1 发送给每一个有权限接收该信号的进程;<-1 pid=某个进程组的ID取反 - sig: 需要发送信号的编号或宏值,0表示不发送任何信号 int raise(int sig); - 作用:给当前的进程发送信号 - sig: 要发送的信号 - 返回值:成功返回0,失败返回非0 - kill(getpid(), sig); void abort(void); - 作用:发送SIGABRT信号给当前进程,杀死当前进程 - kill(getpid(), SIGABRT); #include <unistd.h> unsigned int alarm(unsigned int seconds); - 作用:设置定时器,函数调用,开始倒计时,当倒计时为0时,函数会给当前的进程发送一个信号:SIGALARM - seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发送信号)。取消一个定时器,通过alarm(0)。 - 返回值:之前没有定时器返回0,之前有定时器返回之前的定时器剩余的时间 -SIGALARM:默认终止当前进程,每一个进程都有且只有唯一一个定时器。alarm(100) -> 该函数是非阻塞的 定时器,与进程的状态无关(自然定时法),无论进程处于什么状态,alarm都会计时。 int setitimer(int which, const struct itimerval *new_val, struct itimerval *old_val); - 作用:设置定时器。可以代替alarm函数,精读微秒,可以实现周期性定时 - which: 定时器漆以什么时间计时,ITIMER_REAL 真实时间,时间到达,发送SIGALRM;ITIMER_VIRTUAL 用户时间,时间到达,发送SIGVTALRM;ITMIER_PROF 以该进程在用户态和内核态所消耗的时间计算, 时间到达,发送SIGPROF; - new_val: 设置定时器的属性 - old_val: 上一次定时的时间参数,一般不使用指定为NULL - 返回值:成功返回0,失败返回-1并设置errno struct itimerval { // 定时器 struct timeval it_interval; // 周期时间 struct timeval it_value; // 第一次延迟多长时间执行定时器 }; struct timeval { // 时间 time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒 };
11.运行程序的时间
实际的时间 = 内核时间 + 用户时间 + 消耗的时间
进行文件IO操作的时候比较浪费时间
12.信号捕捉。信号捕捉要注册在信号之前。
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); - 作用:设置某个信号的捕捉行为 - signum: 要捕捉的信号 - handler: 捕捉到的信号要如何处理,SIG_IGN:忽略信号;SIG_DFL:使用信号默认的行为;回调函数:这个函数是系统调用,程序员只负责写,捕捉到信号后如何处理信号 - 返回值:成功返回上一次注册的信号处理函数的地址,第一次调用返回NULL;失败返回SIG_ERR,并设置错误号 - 回调函数:程序员提前实现,函数类型根据需求,看函数指针定义;当信号产生时,由内核调用;函数指针是实现回调的手段,函数实现后,将函数名放到函数指针的位置 int sigaction(int signum, const struct sigaction *act, struct sigaction *odlact); - 作用:检查或者改变信号的处理。信号捕捉 - signum: 需要捕捉的信号的编号或者宏值 - act: 捕捉到信号之后的处理动作 - oldact: 上一次对信号捕捉相关的设置,一般设置为NULL - 返回值:成功返回0,石板返回-1 struct sigaction { void (*sa_handler)(int); // 函数指针,指向的函数是信号捕捉到之后的处理函数 void (*sa_sigaction)(int, siginfo_t *, void *); // 不常用 sigset_t sa_mask; // 临时阻塞信号集,信号捕捉函数执行过程中临时阻塞某些信号 int sa_flags; // 使用哪一个信号处理对捕捉到的信号进行处理,0表示使用sa_handler,SA_SIGINFO表示使用sa_sigaction void (*sa_restorer)(void); // 被废弃了 };
13.信号集。多个信号课使用一个称之为信号集的数据结构表示,系统数据类型为 sigset_t。PCB中有两个信号集。
-
阻塞信号集, 信号的阻塞是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感操作。
-
未决信号集,信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的一段时间。
-
这两个信号集都是内二使用位图机制实现的,无法直接进行操作。需要借助信号集操作函数来对这两个信号集修改。
14.信号处理过程。
-
用户通过键盘 Ctrl + C,产生2号信号SIGINT(信号被创建)
-
信号产生但是没有被处理(未决)
-
在内核中将所有没有被处理的信号存储在一个集合中(未决信号集)
-
SIGINT信号状态被存储在第二盒标志位上
-
这个标志位的值为0,表示信号不是未决
-
这个标志位的值为1,表示信号处于未决
-
这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较:
-
阻塞信号集默认不阻塞任何信号集
-
如果想要阻塞某些信号需要用户调用系统API
-
在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置了阻塞:
-
如果没有阻塞,这个信号被处理
-
如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
// 以下信号集相关的函数都是对自定义的信号集进行操作。 #include <signal.h> int sigemptyset(sigset_t *set); - 作用:清空信号集中的数据,将信号集中的所有标志位置为0 - set: 传出参数,需要操作的信号集 - 返回值:成功返回0,失败返回-1并设置errno int sigfillset(sigset_t *set); - 作用:将信号集中的所有标志位置为1 - set: 传出参数,需要操作的信号集 - 返回值:成功返回0,失败返回-1并设置errno int sigaddset(sigset_t *set, int signum); - 作用:设置信号集中的某一个信号对应的标注为1,表示阻塞该信号 - set: 传出参数,需要操作的信号集 - signum: 需要设置阻塞的信号 - 返回值:成功返回0,失败返回-1并设置errno int sigdelset(sigset_t *set, int signum); - 作用:设置信号集中的某一个信号对应的标注为0,表示不阻塞该信号 - set: 传出参数,需要操作的信号集 - signum: 需要设置不阻塞的信号 - 返回值:成功返回0,失败返回-1并设置errno int sigismember(const sigset_t *set, int signum); - 作用:判断某个信号是否属于信号集set - set: 需要操作的信号集 - signum: 需要判断的信号 - 返回值:属于返回1,不属于返回0,调用失败返回-1并设置errno
15.处理系统信号集。(只能改变内核阻塞信号集,不能改变未决信号集)
int sigprocmask(int how, cosnt sigset_t *set, sigset_t *oldset); - 作用:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换) - how: 如何对内核阻塞信号集进行处理, - SIG_BLOCK 将用户设置的阻塞信号集添加到内核中,内核原来的数据不变,即:mask | set;(假设内核中默认阻塞信号集为mask) - SIG_UNBLOCK 根据用户设置的数据,对内核中的数据进行解除阻塞,即:mask &= ~set - SIG_SETMASK 覆盖内核中原来的值 - set: 已经初始化好的用户自定义的信号集 - old_set: 保存设置之前的内核中的阻塞信号集的状态,可以使NULL - 返回值:成功返回0,失败返回-1并设置错误号:EFAULT、EINVAL int sigpending(sigset_t *set); - 作用:获取内核中的未决信号集 - set: 传出参数,保存的是内核中的未决信号集中的信息
16.以下三种条件都会给父进程发送SIGCHILD信号,父进程默认忽略该信号。SIGCHILD信号产生的条件:
-
子进程终止
-
子进程接收到SIGSTOP信号停止时
-
子进程处在停止态,接收到SIGCONT后唤醒
17.在父进程注册完信号捕捉之前,子进程就结束了,会出现段错误。所以需要提前设置好阻塞信号集,阻塞SIGCHILD。
sigset_t set; sigemptyset(&set); sigeaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除阻塞