APUE_第十章 信号_学习笔记

本文详细介绍了Linux系统中的信号机制,包括信号的概念、信号处理方式、signal函数、不可靠信号、中断的系统调用、可重入函数、SIGCHLD信号的语义以及sigaction函数等关键知识点,深入探讨了信号在进程控制和异步事件处理中的应用。
摘要由CSDN通过智能技术生成

10.1 信号

1) 信号是软件中断,信号机制提供的是一种处理异步事件的方法;

10.2 信号概念

1) 在头文件<signal.h> 中,信号被定义为正整数(信号编号);不存在编号为0 的信号,kill函数对编号为0的信号有特殊应用(将该信号发送给同一进程组中的所有进程);
2) 产生信号的条件:

  1. 在终端上键入,引起中断产生信号;
  2. 硬件引起的信号,除数为0, 无效的内存引用。硬件检测到这些,并发送给内核, 内核将相关信号发送给进程;(无效内存:SIGSEGV)
  3. 进程调用*kill函数发送信号给其他进程,有所限制:信号发射进程和信号接收进程都属于同一个用户, 或是信号发送者的具有超级权限;
  4. 此外,用户可用kill命令将信号发送给其他进程
  5. 当检测到某种软件条件发生, 会将信号通知给有关进程。例如网络连接上传来外带数据的时候(SIGURG), 在管道上读进程终止后, 一个进程写此管道是产生(SIGPIPE), 进程设置的闹钟时钟超时(SIGALRM);

3) 为什么信号是异步的,产生信号的事件对于进程而言是随机出现的。进程并不能预测产生信号的事件什么时候产生;并且进程可以阻塞信号,等条件允许后在处理信号;

4) 进程对信号的处理有三种选择:

  1. 忽略该信号;(SIGKILL, SIGSTOP这两个信号不能被忽略,这两个信号向超级用户提供了可靠的进程终止或停止的方法,不能忽略是为了保证可靠性)——SIGIGN
  2. 执行系统默认的信号处理程序;——SIGDFL
  3. 执行用户自定义的信号处理程序;——自定义信号处理程序

5) core文件,复制进程的存储映像到进程当前工作目录下面的core文件;

6) 常见信号:

  1. SIGCONT:此信号发送给需要继续运行,但是目前处于停止状态的进程;如果进程是运行着的,收到ISGCONT信号系统默认行为是忽略; 如果进程是停止的,收到SIGCONT信号系统默认行为是继续执行;
  2. SIGEMT:指示一个实现定义的硬件故障;
  3. SIGFPE: 算术运算异常, 除以0, 浮点溢出;
  4. SIGHUP:中断设备检测到一个连接断开, 将此信号发送给与终端有关的控制进程(会话首进程)(只有当终端没有设置CLOCAL标志,才会产生SIGHUP信号);此时会话首进程可以在后台运行(区别于由终端产生的中断、退出和挂起信号,这三个信号只能传递给前台进程组);
    会话首进程终止,也会产生SIGHUP信号, 此信号发送给每一个前台进程;
    可以使用SIGHUP信号通知守护进程;(第十三章内容);
  5. SIGINFO 当用户按状态键的时候(Ctrl + T),终端驱动程序将产生此信号,并将SIGINFO信号发送给前台进程;此信号将显示前台进程组站哪个的各个进程显示进程状态信息;
  6. 挡在一个可轮询设备上发生一个特定事件的时候产生此信号;
  7. SIGSEGV: 进程进行了一次无效内存引用;
  8. SIGSTOP:作业控制信号, 用于停止一个进程;(不可忽略)
  9. SIGTSTP:交互式停止信号, 当用户在终端上键入挂起按键(Ctrl Z), 终端驱动程序产生此信号, 将该信号送至前台进程组所有进程;

10.3 signal函数

void (*signal(int signo, void (*func)()))(int signo);	//函数返回值指向之前的信号处理程序的指针
#define void Sigfunc(int)	//函数的签名有两项:函数参数类型和函数返回类型
Sigfunc *signal(int signo, Sigfunc *func);
#define SIG_ERR (void (*)())-1	//将0xFFFFFFFF 地址转化为Sigfunc函数指针类型,表示出错
#define SIG_DFL (void)(*)())0	//类型强制转化
#define SIG_IGN (void)(*)())1	//括号里面是否应该加上int??
//-1, 0, 1 这些常量必须是三个值,能够代表一个地址,并且这三个地址决不能是任意可声明函数的地址。大多数UNIX系统使用这三个值表示特殊情况;

1) 程序启动
当执行那个一个程序的时候, 所有信号的状态都是系统默认或忽略。 一般所有的信号都被设置为默认动作,除非调用exec进程忽略该信号。 exec函数将原先设置为要捕抓的信号更改为默认动作(因为当exec一个新程序之后, 原先的信号捕抓函数的地址就没用了,新程序不认识)
例如,在后台执行程序,shell自动将后台程序的中断和退出信号方式设置为忽略。于是中断键就不会影响后台进程;
很多程序的代码要先检测系统对终端信号的处理方式,如果进程的某一信号设置为忽略,就不会设置信号处理程序,当信号不是被设置为忽略的时候, 就设置信号处理程序;

2) signal函数的限制,不改变信号的处理方式就不能确定信号的当前处理方式。sigaction函数没有这个限制,signal函数的底层实现是sigaction函数;

void sig_int(int); void sig_quit(int);
if(signal(SIGINT, SIG_IGN) !=SIG_IGN)
	signal(SIGINT, sig_int);
if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
	signal(SIGQUIT, sig_quit);		

3) 当一个进程调用fork的时候, 其子进程继承父进程的信号处理方式。
4) 总结(使用exec的时候,原先的信号处理方式丢失; 使用fork的时候,子进程继承父进程的信号处理方式);

10.4 不可靠的信号

1) 信号的不可靠,是指哪一些方面:

  1. 信号可能会丢失,进程不知道有信号;
  2. 进程对信号的控制能力很差, 有时候进程希望阻塞一个信号(不是忽略该信号,而是当接收到信号的时候,记住这个信号,进程在执行一段时间做好准备后, 再回头处理该信号);——早期的unix的进程没有阻塞信号的能力;
  3. 早期的UNIX有一个问题, 会在进程每次接收到信号的时候,对信号处理时,将对该信号的动作复位为默认值。因而可以在信号处理程序中,再次调用signal函数;其进程对该信号的处理方式是:系统默认-自定义信号处理-系统默认-…, 进程可能在接收到某一信号的时候其信号处理方式是系统默认。某些系统默认处理方式是终止进程,导致出错;
int sig_int();
signal(SIGINT, sig_int);
sig_int()
{
   
	signal(SIGINT, sig_int); //当系统复位信号动作为默认值,使用signal重新设定信号处理程序
}

10.5 中断的系统调用

1)早期UNIX的特点:如果进程在执行一个低速系统调用而阻塞,阻塞期间捕抓到一个信号。这个阻塞的系统调用就会被中断而不再继续执行;并设置errno为EINTR;
2) 低速系统调用,低速系统调用是可能使进程永远阻塞的系统调用:

  1. 在读某些文件时候,如果数据并不存在,会阻塞;
  2. 在写某些文件时候,如果接收者不能立即接受这些数据,调用者会阻塞;
  3. 打开某些文件,在某种条件发生之前可能会使调用者阻塞(例如打开终端,需要等待调制解调器应答电话);
  4. pause,使调用进程休眠直到捕抓到一个信号;
  5. 某些ioctl操作
  6. 某些进程间通信;
    3) 读磁盘IO的系统调用虽然会阻塞,但不可能永远被阻塞,不是低速系统调用;
    4) 被中断的系统调用,必须显式地处理出错返回;例如,一个读操作,被中断之后,我们希望重启这个系统调用;
    5) 为了简化用户的程序, 4.2BSD引入了被中断的系统调用的自动重启的功能; 具有自动重启的功能的系统调用包括ioctl, read, readv, write, writev, wait 和 waitpid。前5个函数只有对低速设备进行操作的时候才会被信号中断(捕抓到信号 + 低速操作 = 中断), 后两个函数wait, waitpid只有在捕抓到信号就会中断(捕抓到信号 = 中断)
    有些系统调用并不希望被中断后重启,这些系统调用可以选择禁用系统调用的自动重启功能;
    6) 引入自动重启的功能的原因是:
    有时候用户并不知道使用的IO设备是低速设备,如果捕抓到信号,但是没有自动重启的功能,用户程序需要检测出错返回,如果出错返回表面是被中断的,用户程序再调用读、写系统调用(注意,只有在访问低速设备的时候,系统调用才会被中断;访问告诉设备的时候,系统调用立即返回);

10.6 可重入函数

1) 可重入函数
考虑两个情况, 如果继承在使用malloc的时候接收到了一个信号,跑去处理信号处理程序了信号处理程序又调用malloc,会如何?
如果进程在执行getpwnam,其结果放在静态存储单元中, 如果期间执行信号处理程序,又调用getpwnam,则之前的静态存储区的信息会被信号处理程序中的信息所覆盖;
这类函数就是不可重入函数;

2) 不可重入函数的原因是——主函数和信号处理函数之间没有同步机制,存在竞争关系:

  1. 主函数和信号处理函数都使用了静态数据结构;(这个静态数据结构可能被竞争)
  2. 主函数和信号处理函数调用了malloc和free;(分配内存的时候可能会被破坏)
  3. 信号处理函数调用了标准IO函数, 标准IO库很多的实现都是不可重入;

3) **即便是可重入函数,由于每一个线程只有一个errno, 所以信号处理函数可能会修改errno的原先值;**例如一个main函数刚刚设置了errno后进入信号处理函数, 信号处理函数中调用read修改了errno的值,取代了main设置的值;
4) 通用规则:当在信号处理函数中调用了可冲入函数的时候, 应当事先保存errno的值, 之后恢复errno的值。

10.7 SIGCHLD语义

1) 子进程状态改变后,向其父进程发送该信号;早期对SIGCHLD信号的处理:
2) 如果进程设定对SIGCHLD信号的处理是SIG_IGN(忽略SIGCHLD信号), 则子进程不会是僵尸进程;
3) 注意区分忽略该信号(SIG_IGN)和该信号的默认动作(SIG_DFL)是忽略这两者的关系:忽略该信号,子进程正常终止,不会有僵尸进程; 该信号的默认动作是忽略,表面丢弃子进程终止状态,子进程是僵尸进程)如果调用进程随后调用一个wait函数,进程将阻塞到所有子进程都终止,然后wait返回-1.并将errno设置为ECHILD;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值