Unix_Linux操作系统-笔记Day6(信号)

Day6

信号的基本概念

  1. 中断,中止(非终)当前正在运行的任务,转而执行其他任务(可能返回也可能不返回),中断分为硬件中断(硬件设备产生的中断)和软件中断(其他程序产生的中断)

  2. 信号:是一种软件中断,提供了一种异步执行任务的机制

  3. 常见的信号

    • SIGINT(2) Ctrl+C
    • SIGQUIT(3) Ctrl+\
    • SIGABRT(6) 调用abort函数,产生此信号
    • SIGFPE(8) 表示一个算术运算异常,例如除以0、浮点溢出等
    • SIGKILL(9) 不能被捕获或忽略。常用于杀死进程
    • SIGSEGV(11) 试图访问未分配的内存,或向没有写权限的内存写入数据
    • SIGCHLD(17) 在一个进程终止或停止时,将此信号发送给其父进程
    • SIGTSTP(20) Ctrl+Z
  4. 不可靠信号
    kill -l可以显示所有信号

    建立在早期机制上的信号被称为不可靠信号,1~31

    不支持排队可能丢失,同一个信号产生多次,系统可能只接收到一次

  5. 可靠信号
    采用新的机制产生的信号,34~64

    支持排队,不会丢失

  6. 信号的来源

    • 硬件产生:除0,非法内存访问

      这些异常是硬件(驱动)检测到,并通知内核,内核再向引发这些异常的进程发送相应信号

    • 软件产生:通过kill/raise/alarm/setitmer/sigqueue函数

  7. 信号的处理

    • 忽略
    • 终止
    • 终止进程并产生core文件
    • 捕获信号并处理
信号 解释 产生条件 默认动作
SIGHUP(1) 连接断开信号 如果终端接口检测一个连接断开,则将此信号发送给与该终端相关的控制进程(会话首进程) 终止
SIGINT(2) 终端中断符信号 用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程 终止
SIGQUIT(3) 终端退出符信号 用户按退出键(Ctrl+\),产生此信号,并送至前台进程组的所有进程 终止+core
SIGILL(4) 非法硬件指令信号 进程执行了一条非法硬件指令 终止+core
SIGTRAP(5) 硬件故障信号 指示一个实现定义的硬件故障。常用于调试 终止+core
SIGABRT(6) 异常终止信号 调用abort函数,产生此信号 终止+core
SIGBUS(7) 总线错误信号 指示一个实现定义的硬件故障,常用于内存故障 终止+core
SIGFPE(8) 算术异常信号 表示一个算术运算异常,例如除以0、浮点溢出等 终止+core
SIGKILL(9) 终止信号 不能被捕获或忽略。常用于杀死进程 终止
SIGUSR1(10) 用户定义信号 用户定义信号,用于应用程序 终止
SIGSEGV(11) 段错误信号 试图访问未分配的内存,或向没有写权限的内存写入数据 终止+core
SIGUSR2(12) 用户定义信号 用户定义信号,用于应用程序 终止
SIGPIPE(13) 管道异常信号 写管道时读进程已终止,或写SOCK_STREAM类型套接字时连接已断开,均产生此信号 终止
SIGALRM(14) 闹钟信号 以alarm函数设置的计时器到期,或以setitimer函数设置的间隔时间到期,均产生此信号 终止
SIGTERM(15) 终止信号 由kill命令发送的系统默认终止信号 终止
SIGSTKFLT(16) 数协器栈故障信号 表示数学协处理器发生栈故障 终止
SIGCHLD(17) 子进程状态改变信号 在一个进程终止或停止时,将此信号发送给其父进程 忽略
SIGCONT(18) 使停止的进程继续 向处于停止状态的进程发送此信号,令其继续运行 继续/忽略
SIGSTOP(19) 停止信号 不能被捕获或忽略。停止一个进程 停止进程
SIGTSTP(20) 终端停止符信号 用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程 停止进程
SIGTTIN(21) 后台读控制终端信号 后台进程组中的进程试图读其控制终端,产生此信号 停止
SIGTTOU(22) 后台写控制终端信号 后台进程组中的进程试图写其控制终端,产生此信号 停止
SIGURG(23) 紧急情况信号 有紧急情况发生,或从网络上接收到带外数据,产生此信号 忽略
SIGXCPU(24) 超过CPU限制信号 进程超过了其软CPU时间限制,产生此信号 终止+core
SIGXFSZ(25) 超过文件长度限制信号 进程超过了其软文件长度限制,产生此信号 终止+core
SIGVTALRM(26) 虚拟闹钟信号 以setitimer函数设置的虚拟间隔时间到期,产生此信号 终止
SIGPROF(27) 虚拟梗概闹钟信号 以setitimer函数设置的虚拟梗概统计间隔时间到期,产生此信号 终止
SIGWINCH(28) 终端窗口大小改变信号 以ioctl函数更改窗口大小,产生此信号 忽略
SIGIO(29) 异步I/O信号 指示一个异步I/O事件 终止
SIGPWR(30) 电源失效信号 电源失效,产生此信号 终止
SIGSYS(31) 非法系统调用异常 指示一个无效的系统调用 终止+core

信号的捕获

sighandler_t signal(int signum, sighandler_t handler);

  • 信号处理注册函数
  • signum 信号的编号,1~31 也可以是宏
  • handler
    • 函数指针
      • typedef void (*sighandler_t)(int);
      • SIG_IGN 忽略该信号
      • SIG_DEL 默认处理

注意 在某些UNIX系统上,signal注册的函数只执行一次,执行完后恢复默认处理方式,如果想长期使用处理函数处理,在处理函数结尾再注册一遍

  • SIGTSTP(20) 可以被捕获,但不能被处理
  • SIGKILL(9)/SIGSTOP(19) 既不能被捕获,也不能被处理
  • SIGSTOP信号可以让进程暂停,当再次收到SIGGCONT信号时会继续执行
  • 普通用户只能给自己的进程发送信号,root可以给任何用户
  • 在中断给进程传送信号kill -[信号] [PID]

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

homework

#include 
#include 
#include 
#include 

void sigfunc(int signum){
    printf("我是进程%u,我收到了内核发送的%d\n",getpid(),signum);
    exit(0);
}

int main(){

    signal(SIGSEGV,sigfunc);
    int* p = NULL;
    *p = 20;    //信号处理完后返回到收到信号前的那行
    for(;;);
}

发送信号

  1. 键盘
    • Ctrl+c SIGINT(2)
    • Ctrl+\ SIGQUUIT(3)
    • Ctrl+z SIGTSTP(20)
  2. 错误
    • 除0 SIGFPG(8)
    • 非法访问内存 SIGSEGV(11)
  3. 命令
    • kill -signum pid
    • ps -aux 查看所有进程
  4. 函数
    • int kill(pid_t pid, int sig);
      • 向指定的进程发送信号
      • pid 进程id
        • pid > 0 向进程号为pid的进程发送信号
        • pid = 0 向同一进程组的进程发送信号
        • pid = -1 向所有(有权)进程发送信号
        • pid < -1 向进程号为abs(pid)的进程组发送信号
      • sig 信号的编号
        • 值为0时,kill不会发送信号,但号进行错误检查(检查进程号或进程组id号是否操作)
    • int raise(int sig);
      • 向当前进程(自己)发送信号

暂停与休眠

int pause(void);

  • 一旦执行就会进入无限的休眠(暂停),直到遇到信号
  • 先执行信号处理函数才会从休眠中醒来

unsigned int sleep(unsigned int seconds);

  • 休眠指定的秒数,当有信号来临时会提前醒来,提前醒来会返回剩余的秒数,或者睡够了,返回0

闹钟

unsigned int alarm(unsigned int seconds);

  • 告诉内核在seconds后向进程发送SIGALRM信号
  • 如果之前设定的时间还没有到,则会覆盖,并返回之前设定的剩余秒数

信号集与信号屏蔽

  • 信号集 信号的集合,由128位二进制组成,每一位代表一个信号

    • int sigemptyset(sigset_t *set);

      • 清空信号集,把所有位设置为0
    • int sigfillset(sigset_t *set);

      • 填满信号集,把所有位设置为1
    • int sigaddset(sigset_t *set, int signum);

      • 向信号集中添加一个信号
    • int sigdelset(sigset_t *set, int signum);

      • 从信号集中删除一个信号
    • int sigismember(const sigset_t *set, int signum);

      • 判断信号集中是否有signum信号
  • 信号屏蔽 当做一些特殊操作时,会希望有些信号来,而有些信号不要来,而与设置信号忽略 不同 的是:信号屏蔽只是暂时不来,而可以获取到着一段时间发生了哪些信号

    • 每一个信号都有一个信号掩码(信号集),其中包括了需要屏蔽的信号,可以通过sigprocmask函数检查,修改进程的信号掩码
    • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
      • 检查修改进程的信号掩码
      • how 可以为空(获取当前的信号掩码)
        • SIG_BLOCK 设置当前信号掩码与set的并集为新的信号掩码,添加
        • SIG_UNBLOCK 新的信号掩码是当前掩码与set补集的交集,删除
        • SIG_SETMASK 把set当作新的信号掩码,更新
      • old 旧的信号屏蔽掩码
    • int sigpending(sigset_t *set);
      • 获取信号屏蔽期间发生的未处理的信号,当信号屏蔽解除后就没有了

    注意 在信号屏蔽期间发生的不可靠信号只捕获一次

练习2 学生信息管理系统,在保存数据和加载数据时屏蔽Ctrl+c Ctrl+,等数据加载,保存完后再处理该信号

homework

带附加信息的信号捕获

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_NOCLDSTOP    //当子进程暂停时,不通知父进程
        SA_NOCLDWAIT    //
        SA_NODEFER      //当执行信号处理函数时,不屏蔽正常处理的信号
        SA_ONSTACK      //
        SA_RESETHAND    //信号只处理一次就恢复默认处理方式
        SA_RESTART      //系统调用如果被signum信号中断,自行重启
        SA_RESTORER     //
        SA_SIGINFO      //使用第二个函数指针处理信号
        */
    void     (*sa_restorer)(void);   //保留
};
#include 
#include 
#include 

void sa_sigint(int sig, siginfo_t * info, void *ptr){
    printf("%d,%s,%p",sig,info->si_ptr,ptr);
}


int main(){
    struct sigaction act = {};
    act.sa_signation = siginit;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGINT,&act,NULL);


        union sigval value;
        value.sival_ptr = "hehe";
        sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

}


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

  • 信号发送函数,与kill不同的是,可以复加一些额外数据
  • pid 目标进程号
  • sig 要发送信号
  • value 联合,成员可以是整数或指针

计时器

  1. 系统为每个进程维护3个计时器
    • ITIMER_REAL 真实计时器,程序运行实际所用时间
    • ITIMER_VIRTUAL 虚拟计时器,程序运行在用户态所消耗时间
    • ITIMER_PROF 实用计时器,程序在用户态和内核态所消耗的时间
    • 实际时间(真实计时器) = 用户时间(虚拟) + 内核 + 睡眠时间

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

  • 获取当前进程的定时器
  • which 选择使用哪一种定时器
  •   struct itimerval {
          struct timeval it_interval; /* Interval for periodic timer */
          struct timeval it_value;    /* Time until next expiration */
      };
      struct timeval {
          time_t      tv_sec;         /* seconds */
          suseconds_t tv_usec;        /* microseconds 0~999999 */
      };

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

  • 给当前进程设置定时器,
#include 
#include 
#include 
#include 
void sigalrm(int sig){
    printf("its time\n");
}

int main(){
    signal(SIGALRM,sigalrm);
    //struct timeval it_value = {0,999999};
    //struct timeval it_interval = {0,999999};
    struct itimerval curr_value = {{0,999999},{0,999999}};
    setitimer(ITIMER_REAL,&curr_value,NULL);
    for(;;);
}


练习 使用setitimer实现秒表

homework

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

  1. 由写入状态切换到读取状态
  2. 遇到\n符
  3. 缓冲区满4k
  4. 手动刷新fflush(FILE*)
  5. 文件关闭

你可能感兴趣的:(笔记)