Unix/Linux操作系统-信号处理

一、信号的基本概念

  1. 中断:中止当前正在执行的任务,转而执行其他任务(返回或不返回)。
    1.1 硬件中断:硬件设备产生的中断(键盘按键产生的中断)。
    1.2 软件中断:其他程序产生的中断。
  2. 信号:是一种软件中断,提供了一种异步执行任务的中断。
    2.1 常见的信号
    SIGINT(2):终端中断符信号,用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程
    SIGQUIT(3):终端退出符信号,用户按退出键(Ctrl+‘\’),产生此信号,并送至前台进程组的所有进程
    SIGABRT(6):异常终止信号,调用abort函数,产生此信号
    SIGFPE(8):算术异常信号,表示一个算术运算异常,例如除以0、浮点溢出等
    SIGKILL(9):终止信号,不能被捕获或忽略。常用于杀死进程
    SIGSEGV(11):段错误信号,试图访问未分配的内存,或向没有写权限的内存写入数据
    SIGCHLD(17):子进程状态改变信号,在一个进程终止或停止时,将此信号发送给其父进程
    SIGTSTP(20):终端停止符信号,用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程
    注意:在终端执行 kill -l 可以显示出所有的信号
  3. 不可靠信号:建立在早期机制上的信号,SIGHUP(1)~(SIGSYS(31),不支持排队可能会丢失,同一个信号产生多次进程可能只接收到一次。
  4. 可靠信号:采用新的机制产生的信号,SIGRTMIN(34)~SIGRTMAX(64),支持排队,不会丢失。
  5. 信号的来源
    5.1 硬件产生的信号:除0,非法内存访问。这些异常是硬件(驱动)检查到,并通知内核,然后内核再向引发这些异常的进程发送相应的信号。
    5.2 软件产生的信号:通过kill / raise / alarm / setitmer / sigqueue 函数产生。
  6. 信号的处理
    忽略
    终止进程
    终止进程并产生core文件
    捕获信号并出路

二、信号的捕获

#include <signal.h>

sighandler_t signal(int signum, sighandler_t handler);

  • 功能
    • 信号处理注册函数
  • signum
    • 信号的编号,1~31,也可以是宏
  • handler
    • SIG_IGN:忽略该信号
    • SIG_DEL:默认处理
    • 函数指针
  • 返回值
    • 成功返回原来信号函数处理指针,或者返回SIG_IGN / SIG_DEL;失败返回SIG_ERR
  • 注意
    • 在某些UNIX系统上,signal注册的函数只执行一次,执行完后就恢复成默认处理方式。如果想 长期使用该函数处理信号,可以在函数结束前再注册一次。
    • SIGKILL / SIGSTOP:不能被捕获和处理,SIGCONT可以使暂停的信号继续执行
    • 普通用户只能给自己的进程发送信号号,而root可以给任何进程发送信号。

练习1:实现一个“死不掉”的进程,当收到信号后,给出信号产生的原因。

三、发送信号

#include <signal.h>
  1. 键盘
    Ctrl+c SIGINT
    Ctrl+’ \ ’ SIGQUIT
    Ctrl+z SIGTSTP
  2. 错误
    除零 SIGFPE
    非法访问内存SIGSEGV
  3. 命令
    kill -signum(信号) pid(进程号)
    ps-aux 查看所有进程
  4. kill
    int kill(pid_t pid, int sig);
    功能:向指定的进程发送信号
    pid:进程id
    – pid>0向进程号为pid的进程发送信号
    – pid=0向同组的进程组发送信号
    – pid=-1向所有进程发送信号
    – pid<-1向进程组为==abs(pid)==进程发送信号
    sig:信号的编号
    – sig=0:不会发送信号,但是会检查进程号或进程组id号是否存在
    – 1<sig<=64:正常发送信号
    – sig<64:不会发送信号,检查是否是非法信号
  5. raise
    int raise(int sig);
    功能:发送当前进程指定的信号(给自己发送信号)

四、休眠

#include <signal.h>
  1. int pause(void);
    功能:一旦执行进程就会进行无限的休眠(暂停)
    注意
    – 直到遇到信号。先执行信号处理函数才会从休眠中醒来。
    – 正常情况下不会返回,被信号中断,返回-1。
  2. unsigned int sleep(unsigned int seconds);
    功能:休眠指定的秒数,当有信号来临时,会提前醒来,提前醒来会返回剩余的秒数,或者睡够了,返回0.

五、闹钟

#include <unistd.h>
  1. unsigned int alarm(unsigned int seconds);
    功能:告诉内核再seconds秒后,向当前进程发送SIGALRM信号。
    返回值:如果之前设定的时间还没有到,会重新覆盖,并返回之前设置的剩余秒数。

六、信号集与信号屏蔽

  1. 信号集:信号的集合,由128位二进制组成,每一位代表一个信号。
  2. 头文件#include <signal.h>
  3. int sigemptyset(sigset_t *set);
    功能:清空信号集,把所有位设置为0
  4. int sigfillset(sigset_t *set);
    功能:填满信号集,把所有位设置为1
  5. int sigaddset(sigset_t *set, int signum);
    功能:向信号集添加一个信号,置1
  6. int sigdelset(sigset_t *set, int signum);
    功能:在信号集中删除一个信号,置0
  7. int sigismember(const sigset_t *set, int signum);
    功能:判断该信号是否属于该信号集,存在返回1,不存在返回0
  8. 信号屏蔽:当做一些特殊操作时会希望有些信号来或者不来,与信号忽略相比,信号屏蔽可以查看获取已屏蔽信号。每个进程都有一个信号掩码(信号集),其中包括了需要屏蔽的信号。
  9. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    功能:检查修改进程掩码。
    how
    – SIG_BLOCK:设置当前信号集与set的并集为新的信号掩码(添加)
    – SIG_UNBLOCK:新的信号掩码是当前掩码与set的补集的交集(删除)
    – SIG_SETMASK:把set当做新的信号掩码(重新设置)
    set:可以为空,则获取信号掩码
    oldset:旧的信号屏蔽掩码
  10. int sigpending(sigset_t *set);
    功能:获取信号屏蔽期间发生的信号,当信号屏蔽解除后就没有了
    注意:在信号屏蔽期间发生的信号,无论多少次,只能捕获一次

七、带附加信息的信号捕获

#include <signal.h>
  1. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
    • 功能:向内核注册信号处理函数
    • signum:信号编码
    • act:以结构体方式保存信号处理方式
    • oldact:获取到此信号旧的处理方式,可以为NULL
struct sigaction{    
	void (*sa_handler)(int);//简单的信号处理函数指针    
	void (*sa_sigaction)(int, siginfo_t *, void *);//可以带附加信息的信号处理函数指针    
	sigset_t sa_mask;//当执行信号处理函数时,需要屏蔽的信号    
	int sa_flags;        
		SA_RESETHAND//表示信号只处理一次,然后就恢复默认处理方式        
		SA_RESTART//系统调用如果被signum信号中断,自行重启        
		SA_NOCLDSTOP//当子进程暂停时,不用通知父进程。        
		SA_NODEFER//当执行信号处理函数时,不屏蔽正常的处理的信号              
		SA_SIGINFO//使用第二个函数指针处理信号    
	void (*sa_restorer)(void);//保留,暂不使用
};
  1. int sigqueue(pid_t pid, int sig, const union sigval value);
    • 功能:信号发送函数,与kill不同的是可以附加一些额外的数据
    • pid:目标进程号
    • sig:要发送信号
    • value:联合。成员可以是整数或指针
union sigval{    
	int   sival_int;    
	void *sival_ptr;
};

八、计时器

#include <sys/time.h>
  1. 系统为每个进程维护三个计时器

    • ITIMER_REAL:真实计时器,程序运行的实际所用时间
    • ITIMER_VIRTUAL:虚拟计时器,程序运行在用户态所消耗的时间
    • ITIMER_PROF:实用计时器,程序在用户态和内核态所消耗的时间
  2. 实际时间(真实计时器)=用户时间(虚拟)+内核时间+睡眠时间

  3. int getitimer(int which, struct itimerval *curr_value);

    • 功能:获取当前进程的计时器
    • which:选择使用哪些计时器
  4. int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

    • 功能:给当前进程设置计时器,与alarm的区别更精确
    • which:选择使用哪些计时器
struct itimerval{
	struct timeval it_interval; 每一次触发时钟信号所需要的时间
	struct timeval it_value;  第一次触发时钟信号所需要的时间
};
struct timeval{
    long tv_sec;                /* seconds */
    long tv_usec;        /* microseconds */
};

练习:使用setitimer实现秒表的功能

由于文件读写时为了提高效率,增加了缓冲区,所以当进行写操作时,数据并没有立即写入文件,而是暂时存储到缓冲区中,只有达到某些条件时才写入文件

  • 由写入状态切换到读取状态
  • 遇到\n符号
  • 缓冲区满4k
  • 手动刷新fflush(FILE*)
  • 文件关闭
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值