进程信号

进程信号

一、信号的基本概念

生活中的信号:红绿灯

信号更多的是通知事件的发生,信号产生以后第一时间也不是直接处理而是先存储下来,处理信号。

这节课学习的信号实际是一个软中断。

linux下62种信号(宏),使用kill -l查看。

信号分为两类:1-31不可靠信号(非实时信号)  34-64可靠信号(实时信号)

信号的产生->信号的注册->信号的注销->信号的处理。

二、产生信号

1.通过终端按键产生信号(硬件中断)ctrl + c

2.调用系统函数向进程发信号(程序异常)《unix环境高级编程》

3.由软件条件产生信号

int kill(pid_t pid, int sig); 向指定进程发送指定信号

kill (getpid(), SIGINT);

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

raise(SIGTREM);

void abort(void);向自身发送SIGABRT信号

unsigned int alarm(unsigned int seconds);在n秒之后向进程发送SIGALRM信号

alarm(3);设置一个定时器,取消上一个定时,并且返回上一个定时器剩余时间。

     int sigqueue(pid_t pid, int sig, const union sigval value);

核心转储(core dump;

保存当前程序运行的数据以及调用栈信息,用于错误原因定位调试。

如果程序运行出现错误,可以直接通过core文件来gdb调试(有些错误是偶然发生的)

coredump默认关闭:隐私安全/资源占用

coredump打开:ulimit

三、信号注册

信号的注册/给一个进程发送信号,就是修改这个进程pcb关于信号的pending位图,将相应的信号置为1。

 

四、信号阻塞

暂时不处理信号(阻止信号递达),并不是不接收信号。

信号的递达:信号的处理

要阻塞一个信号,那么就是将pcb中关于信号的block位图,将相应的信号置为1,这个位图就像一个备注,说明若接收到信号暂时不处理。

信号未决:这是一种状态,信号从注册成功到信号递达之间的一种状态。

信号阻塞的实现:

阻塞这个集合中的信号:sigset_t  new_block;

用于保存原来阻塞集合中的信号,防止后续要将阻塞集合还原回去:

sigset_t old_block;

int sigemptyset(sigset_t *set);清空一个信号集合

int sigfillset(sigset_t *set);将所有的信号都添加到set集合中

int sigaddset(sigset_t *set, int sigunm);添加指定的单个信号到set集合中

int sigdelset(sigset_t *set, int signum);从集合中一处一个指定的信号

int sigismember(const sigset_t *set, int signum);判断一个信号是否在一个集合中

int sigprocmask(int how, sigset_t *set, sigset_t *oldset);阻塞信号/接触阻塞

阻塞信号/解除阻塞

how:

SIG_BLOCK 阻塞集合中的信号

SIG_UNBLOCK 将集合中的信号解除阻塞

SIG_SETMASK 将集合中的信号设置到阻塞集合中

set:要阻塞/解除阻塞的集合

oldset:保存原先阻塞集合中的信号

有两个信号是不会被阻塞的:SIGKILL, SIGSTOP

sigpending  获取未决信号

五、注销:

就是从pending集合中将即将要处理的信号相应位置0(从pcb的pending集合中移除)

非可靠信号注册就是将相应pending位图置1,然后添加一个sigqueue结构到链表中,之后如果有相同信号到来,一看位图已经置1了,就不做操作了,意味着后来的信号在前一个信号未处理之前不会重复注册,代表丢了。

可靠信号不管有没有注册都要置1,并且添加节点到链表,所以不会丢信号。

非可靠信号注销就是删除链表节点,相应位图置为0

可靠信号注销时删除节点,判断是否有相同的信号节点,如果没有位图置为0,有就不置为0.

六、处理

默认操作----按照操作系统中对信号事件的既定处理方式

忽略操作----直接将信号丢掉

自定义处理:用户自己定义事件的处理方式

sighandler_t signal(int signum, sighandler_t handler);

signum:信号的编号

handler:处理方式(函数指针数组)

SIG_IGN:忽略                                                                                                                                                                                                                                                                                                                                                                                                                                                           

SIG_DFL:默认

信号的捕捉流程:主要是针对信号的自定义处理方式

信号并不是立即处理的。而是选择一个合适的时机去处理,合适的时机就是当前程序从内核态切换到用户态的时候。

程序如何从用户态切换到内核态?程序异常、中断、系统调用

当我们发起系统调用/程序异常/中断当前程序从用户态运行切换到内核态,去处理这些事情,处理完毕后,要从内核态返回用户态,但是在返回之前会看一下是否有信号需要被处理,如果有就处理(切换到用户态执行信号的自定义处理方式),处理完成后再次返回内核态,判断如果没有信号要处理就调用sys_sigreturn返回用户态(我们程序之前运行的位置)。

signal这个接口有linux版本的差异性,一般用sigaction这个接口替代signal。它的功能也是自定义信号处理方式,signal函数内部也是通过sigaction实现的

struct sigaction {

void (*sa_handler)(int);    处理函数

void (*sa_ sigaction)(int, siginfo_t *,void *);  处理函数

sigset_t  sa_mask;  在处理信号的时候可以通过这个mask暂时阻塞一些信号,处理完毕之后会还原回去

int  sa_ flags; 决定了我们使用哪个回调接口,并且还有一些其他的选项信息

};

重新定义处理方式的信号是:SIGINT  SIGQUIT

int sigaction(int sihnum, const struct sigaction *act, struct sigaction *oldact);

signum:信号编号

act: 新的处理方式

oldact:保存原有的处理方式

这个操作是我们一般性的更改一个信号的处理方式,这种处理方式sa_flags=0代表我们使用的回调接口shisa_handler;

sigemptyset(&new_act.sa_mask);

new_act.sa_flags = 0;

new_act.sa_handler = sigcb;

sigaction(SIGINT, &new_act, &old_act);

 

这个操作是用于传递信号同时携带参数的情况,sa_flags需要被指定为:SA_SIGINFO,并且这时候回调需要使用sa_sigaction

sigemptyset(&new_act.sa_mask);

new_act.sa_flags = SA_SIGINFO;

new_act.sa_sigaction = sigcb1;

sigaction(SIGOUIT, &new_act, &old_act);

int sigqueue(pid_t, int sig, union sigval)这个函数不仅可以发送信号,还可以顺便携带一个参数

pid:进程ID

sig:信号编号

sigval:参数

七、可重入/不可重入函数

不可重入函数:如果函数在不同的地方/时序进行调用,会对函数的功能逻辑造成影响

可重入函数:不管怎么调用,都不会对函数内部功能/程序逻辑造成影响

不可重入函数的要点:

函数内部包含有对全局性变量的修改操作

函数传参的参数跟其他地方共同使用同一变量

因为这些对全局变量的操作不是原子性的,因此这些修改操作有可能在同时在不同地方进行修改

一个函数是否可重入:

1.是否对全局性的数据进行修改

2.这个操作是否是原子的

八、volatile关键字:修饰变量

保持变量内存可见性

一个变量数据被cpu进行处理,每次都要重新从内存中获取变量的数据

如果写的高性能程序需要对程序进行编译优化,那么我们就必须对一些敏感的变量进行内存可见性的修饰

九、僵尸进程的避免

僵尸进程是子进程先于父进程推出,操作系统通知父进程,但父进程不作为。

操作系统通过信号通知父进程(17号信号)

SIGCHLD

以前为避免僵尸进程,只能让父进程一直等待子进程的推出,浪费了父进程资源。

现在信号可以这样做:

自定义信号:SIGCHLD的处理方式,相当于提前告诉进程,当接收到这个信号的时候使用waitpid,这样就不用一直等待。

使用非阻塞的循环来处理SIGCHLD信号,因为SIGCHLD信号不是可靠信号,有可能丢失,因此就有可能漏掉僵尸子进程没有处理,所以一旦接收到信号就处理到不能处理为止。

 

### 信号的概念 信号进程间通信的一种方式,用于通知进程发生了某种特定事件。在 Linux 系统中,有多种常见的信号,每种信号都有其特定的编号和名称,例如 SIGINT(编号 2,通常由 Ctrl+C 触发)、SIGTERM(编号 15,用于正常终止进程)等。信号的管理涉及信号的产生、存储、处理等多个方面,信号的存储结构与进程的状态相关,主要涉及 block 表、pending 表和 handler 表 [^1][^2][^3]。 ### 信号的产生 - **通过键盘产生信号**:用户在终端输入特定的组合键可以产生信号,如 Ctrl+C 会产生 SIGINT 信号,用于终止当前前台进程;Ctrl+\ 会产生 SIGQUIT 信号 [^3]。 - **调用系统函数向进程发送信号**: - **kill**:可以给任意进程发送任意信号,函数原型为 `int kill(pid_t pid, int sig);`,其中 `pid` 是目标进程的 ID,`sig` 是要发送的信号编号 [^1][^3]。 - **raise**:用于给进程本身发送任意信号,函数原型为 `int raise(int sig);`,相当于 `kill(getpid(), sig)` [^1][^3]。 - **abort**:使当前进程异常终止,它会发送 SIGABRT 信号给当前进程,函数原型为 `void abort(void);` [^3]。 - **硬件异常产生信号**:当硬件出现错误时,会产生相应的信号。例如,除零错误会产生 SIGFPE 信号,访问非法内存会产生 SIGSEGV 信号 [^2][^3]。 - **软件条件产生信号**:某些软件条件满足时会产生信号,如 `alarm` 函数可以设置一个定时器,当定时器超时后会产生 SIGALRM 信号 [^1][^3]。 ### 信号的处理方式 - **默认处理**:每个信号都有其默认的处理动作,如终止进程、忽略信号、暂停进程等。 - **忽略处理**:进程可以选择忽略某些信号,即不做任何处理。 - **自定义处理**:进程可以通过信号处理函数来对特定信号进行自定义处理。在 Linux 中,可以使用 `signal` 或 `sigaction` 函数来设置信号的处理动作 [^1][^3]。 ### 信号的捕捉 - **自定义捕捉**:可以使用 `signal` 或 `sigaction` 函数来注册自定义的信号处理函数。例如,使用 `signal` 函数的示例代码如下: ```c #include <stdio.h> #include <signal.h> void handler(int signum) { printf("Received signal %d\n", signum); } int main() { signal(SIGINT, handler); while (1) { // 主循环 } return 0; } ``` - **无法被捕捉的信号**:有些信号是无法被捕捉的,如 SIGKILL(编号 9)和 SIGSTOP(编号 19),它们用于强制终止或暂停进程 [^3]。 - **内核如何实现信号的捕捉**:当信号产生时,内核会在适当的时候检查进程信号处理表,根据信号的处理方式进行相应的处理。如果是自定义处理,会调用注册的信号处理函数 [^3]。 ### 信号的阻塞 - **信号的状态**:信号有三种状态,分别是产生、未决和递达。信号产生后,如果被阻塞,则处于未决状态;当阻塞解除后,信号才会递达并进行处理 [^3][^5]。 - **信号在内核中的表示**:信号在内核中通过 block 表、pending 表和 handler 表来表示。block 表用于记录哪些信号被阻塞,pending 表用于记录哪些信号已经产生但尚未递达,handler 表用于记录信号的处理动作 [^2][^3][^5]。 - **sigset_t 信号集**:`sigset_t` 是一种数据类型,用于表示信号集。可以使用信号集操作函数来对信号集进行操作,如 `sigemptyset`、`sigfillset`、`sigaddset`、`sigdelset` 等 [^2][^3]。 - **sigprocmask 设置阻塞信号集**:`sigprocmask` 函数用于设置进程的阻塞信号集,函数原型为 `int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);` [^1][^3]。 - **sigpending 获取未决信号集**:`sigpending` 函数用于获取当前进程的未决信号集,函数原型为 `int sigpending(sigset_t *set);` [^3]。 ### 信号的补充 - **volatile 关键字**:`volatile` 关键字用于保持内存的可见性,在信号处理函数中使用 `volatile` 可以确保变量的值不会被编译器优化而导致的问题 [^2][^3]。 - **SIGCHLD 信号**:当子进程终止或停止时,会向父进程发送 SIGCHLD 信号。父进程可以通过捕捉该信号来进行资源回收等操作 [^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值