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.信号处理过程。
  1. 用户通过键盘 Ctrl + C,产生2号信号SIGINT(信号被创建)
  2. 信号产生但是没有被处理(未决)
    1. 在内核中将所有没有被处理的信号存储在一个集合中(未决信号集)
    2. SIGINT信号状态被存储在第二盒标志位上
      1. 这个标志位的值为0,表示信号不是未决
      2. 这个标志位的值为1,表示信号处于未决
  3. 这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较:
    1. 阻塞信号集默认不阻塞任何信号集
    2. 如果想要阻塞某些信号需要用户调用系统API
  4. 在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置了阻塞:
    1. 如果没有阻塞,这个信号被处理
    2. 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
// 以下信号集相关的函数都是对自定义的信号集进行操作。
 
#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);      // 解除阻塞

  

 
 
 
 
 
posted @ 2021-08-09 20:42  萌新的学习之路  阅读(32)  评论(0编辑  收藏  举报