Linux 信号的处理、含义、发送和定时信号

Linux 信号的处理、含义、发送和定时信号

文章目录

  • Linux 信号的处理、含义、发送和定时信号
    • 1. 信号的基本概念
    • 2. 信号的分类
    • 3. 常见信号
    • 4. 信号处理
      • 基本信号处理 signal()
      • 指定信号处理 signaction()
      • 进程的阻塞信号 sigprocmask()
    • 5. 信号发送
    • 6. 父子进程的信号处理
    • 7. 系统定时信号
      • alarm()
      • sleep()
      • usleep()
      • setitimer()

1. 信号的基本概念

  • 软中断
  • 目的:
    • 让进程知道发生了某种事件
    • 根据该事件执行相应的动作,即执行它自己代码中的信号处理程序 。
  • 来源:内核
  • 请求方:
    • 进程:通过系统调用 kill 给另一个进程发送信号。进程之间可通过信号通信。
    • 内核:进程 执行出错 时,内核向进程发送一个信号,例如非法段访问、浮点数溢出等,也可通知进程特定事件的发生。
    • 用户:通过输入 Ctrl-C 、 Ctrl-\ 等请求内核产生信号
  • 信号的状态
    • delivery:当进程对信号 采取动作(执行信号处理函数或忽略)时称为递送。
    • pending: 信号产生和递送之间的时间间隔内称信号是未决的
    • block: 进程可指定对某个信号采用递送阻塞。若此时信号处理为默认或者捕捉的,该信号就会处于未决的状态。

2. 信号的分类

  • 根据来源

    • 同步信号:由进程的某个操作产生的信号称为 同步信号 ,例如被零除。

    • 异步信号:用户击键这样的进程 的事件引起的信号称为异步信号 ,该信号产生的事件进程是不可控。

  • 根据处理情况:

    • 不可靠信号
      • 信号值小于 SIGRTMIN。
      • 当同时有多个信号产生时,无法及时处理,造成信号的丢失
      • 早期 Unix 系统中的信号机制比较简单和原始,把那些建立在早期机制上的信号叫做不可靠信号
      • 收到信号的速度超过进程处理的速度的时候,不可靠信号将多余的丢弃掉
    • 可靠信号:
      • 在SIGRTMIN-SIGRTMAX之间
      • 可靠信号将来不及处理的信号就会排入进程的队列
  • 是否支持排队:

    • 实时信号:实时信号都支持排队,都是可靠信号。后 32 种为非实时信号
    • 非实时信号:非实时信号都不支持排队,都是不可靠信号。前 32 种为实时信号。

3. 常见信号

信号名称 信号说明 默认处理
SIGABRT 调用abort 时产生该信号,程序异常结束 进程终止并且产生core 文件
SIGALRM 由alarm 或者 setitimer 设置的 定时器 到期 进程终止
SIGBUS 总线错误,地址没对齐等,取决于具体硬件 进程终止并产生core 文件
SIGCHLD 子进程停止或者终止时, 父进程收到 该信号 忽略该信号
SIGCONT 让停止的进程继续执行 进程终止并且产生core 文件
SIGFPE 算术运算异常,除 0 等 进程终止
SIGHUP 进程的控制终端关闭时产生这个信号 进程终止并且产生core 文件
SIGILL 代码中有非法指 进程终止
SIGINT 终端输入了CTRL+c 信号 下面用 ^c 表示 进程终止
SIGIO 异步I/O ,跟 SIGPOLL 一样 进程终止
SIGIOT 执行I/O 时产生 硬件错 进程终止并且产生core 文件
SIGKILL 该信号用户不能去捕捉和忽略它 进程终止

4. 信号处理

基本信号处理 signal()

目标 简单的信号处理
头文件 signal.h
函数原型 int signal(int signum, void (*action)(int));
参数 signum 需响应的信号
action 如何响应(特殊值:SIG_IGN 忽略信号;SIG_DFL 恢复为默认处理)
返回值 -1 遇到错误
prevaction 返回之前的处理函数指针
  • 不可靠信号:这是早期不可靠信号处理机制造成的。当执行完一次信号处理函数之后,系统的信号处理就恢复为默认处理,如果想让信号处理函数继续有效,必须重新设置

    void sigHandler(int signalNum)
    {
        printf("The sign no is:%d n", signalNum
        signal(SIGINT, sigHandler); //重新设置
    }
    
  • 面临的问题:

    1. 信号处理函数正在执行,没结束时,又产生一个同类型的信号,这时该怎么处理;

    2. 信号处理函数正在执行,没结束时,又发生了一个不同类型的信号,这时该怎么处理;

    3. 进程执行一个 阻塞系统调用如 read() 时,发生了一个信号 ,这时是让该阻塞系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。

    • 例子
    #include 
    #include 
    #include 
    #include 
    #define INPUTLEN    20
    
    char input[INPUTLEN];
    void inthandler(int s) {
        printf("I have Received signal %d .. waiting\n", s);
        sleep(2);
        printf("I am leaving inthandler \n");
        signal(SIGINT, inthandler);
    }
    
    void quithandler(int s) {
        printf("I have Received signal %d .. waiting\n", s);
        sleep(3);
        printf("I am leaving quithandler \n");
        signal(SIGQUIT, quithandler);
    }
    
    int main(){
        signal(SIGINT, inthandler);
        signal(SIGQUIT, quithandler);
        do {
            printf("Please input a message\n");
            int nchars = read(0, input, (INPUTLEN - 1));
            if (nchars == -1) {
                perror("read returned an error");
            }
            else {
                input[nchars] = '\0';
                printf("You have inputed: %s\n", input);
            }
        }while(strncmp(input, "quit", 4) != 0);
    }
    
    
    • 结果
    1. 信号处理函数正在执行,没结束时,又产生一个同类型的信号
      a

    2. 信号处理函数正在执行,没结束时,又发生了一个不同类型的信号
      Linux 信号的处理、含义、发送和定时信号_第1张图片

    3. 进程执行一个 阻塞系统调用如 read() 时,发生了一个信号
      c

指定信号处理 signaction()

目标 指定信号的处理函数
头文件 signal.h
函数原型 int signaction(int signum, const struct sigaction *action, struct sigaction *prevaction);
参数 signum 需处理的信号
action 指向描述操作的结构的指针
prevaction 指向描述被替换操作的结构指针
返回值 -1 遇到错误
0 成功
  • sigation 结构体

    struct sigaction{
    	void (*sa_handler)();
    	void (*sa_sigaction)(int,siginfo_t *,void *);
    	sigset_t sa_mask;
    	int sa_flags;
    } 
    
  • sa_flags的标志

    标记 含义
    SA_RESETHAND 当处理函数被调用时重置,即捕鼠器模式
    SA_NODEFER 处理信号时关闭“信号自动阻塞”(sa_mask无效),因此 允许递归调用 信号处理函数
    SA_RESTART 当阻塞于系统调时收到信号,如果本标志置位,则信号处理函数返回后,系统调用返回失败而需要重新开始,否则系统调用成功返回。主要用于低速设备相关的系统调用。
    SA_SIGINFO 指明使用sa_sigaction 的处理函数值。如果它未设置,则使用旧处理机制, 若设置 ,则 传给处理函数 的包括 信号编号、信号产生的原因和条件等信息

进程的阻塞信号 sigprocmask()

目标 修改当前的信号挡板
头文件 signal.h
函数原型 int sigprocmask (int how, const sigset_t *sigs,sigset_t prev;
参数 how 如何修改信号挡板:SIG_BLOCK, SIG_UNBLOCK,SIG_SET
sigs 指向使用的信号列表的指针
prev 指向之前的信号挡板列表的指针或者为 null
返回值 -1 遇到错误
0 成功
  • 例子

    #include 
    #include 
    
    void sig_handler(int signum, siginfo_t *info, void *myact) {
        if (signum == SIGINT)
            printf("GOT a common signal.\n");
        else
            printf("GOT a real time signal\n");
    }
    
    int main() {
        struct sigaction act;
        sigset_t newmask, oldmask;
        int rc;
    
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGINT);
        sigaddset(&newmask, SIGRTMIN);
        sigprocmask(SIG_BLOCK, &newmask, &oldmask);
        act.sa_sigaction = sig_handler;
        act.sa_flags = SA_SIGINFO;
    
        if (sigaction(SIGINT, &act, NULL) < 0)
            printf("install signal error\n");
        if (sigaction(SIGRTMIN, &act, NULL) < 0)
            printf("install signal error\n");
        printf("pid = %d\n", getpid());
    
        sleep(60);
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
        return 0;
    }
    
    
  • 结果

    image-20221119184910772
    Linux 信号的处理、含义、发送和定时信号_第2张图片

5. 信号发送

  • kill

    目标 向一个进程发送信号
    头文件 signal.h
    函数原型 int kill (pid_t pid , int);
    参数 pid 目标进程
    sig 要被发送的信号
    返回值 -1 遇到错误
    0 成功
  • raise

    目标 向自身进程发送信号
    头文件 signal.h
    函数原型 int raise(intsig);
    参数 sig 要被发送的信号
    返回值 -1 遇到错误
    0 成功
  • sigqueue

    目标 向进程发送信号
    头文件 signal.h
    函数原型 int sigqueue(pid_t pid,int sig,const union sigval value)
    参数 pid 目标进程的 pid
    sig 被发送信号
    参数
    value 为一整型与指针类型的联合体:
    unionsigval{ int sival_int; void* sival_ptr;}
    返回值 -1 遇到错误
    0 成功

6. 父子进程的信号处理

  • 父进程创建子进程时, 子进程继承了父进程信号处理方式, 直到子进程调用 exec 函数

  • 子进程调用 exec 函数后, exec 将父进程中设置为捕捉的信号变为默认处理方式 。

  • 防止僵尸进程产生:

    • 子进程在退出程序时,会向父进程发送 SIGCHLD 信号
    • 父进程在该信号的处理函数中调用 wait 或者 waitpid 获取子进程的退出状态
    • 默认情况下,父进程是忽略该信号的。
  • 例子

    #include 
    #include 
    #include 
    #include 
    
    void intsig_handler(int signum, siginfo_t *siginfo, void *empty) {
        printf("int_handler, my pid=%d\n", getpid());
    }
    
    int main() {
        int pid;
        struct sigaction act;
        act.sa_sigaction = intsig_handler;
        act.sa_flags = SA_SIGINFO;
    
        if (sigaction(SIGINT, &act, NULL) < 0) 
            printf("install signal error\n");
        printf("The parent pid = %d\n", getpid());
        pid = fork();
        if (pid < 0) {
            perror("fork failed.\n"); 
            exit(0);
        }
    
        printf("The return fork = %d\n", pid);
        if (pid == 0) execlp("ls", "ls", NULL);	// if delete
        else									// if delete
            while(1);
    }
    
    • 结果

      Linux 信号的处理、含义、发送和定时信号_第3张图片

    • 若删除上述代码倒数三、四行后

      image-20221119182037854

7. 系统定时信号

alarm()

  • unsigned int alarm(unsigned int seconds);
  • 函数说明 : 用来设置 信号 SIGALRM 在经过参数 seconds 指定的秒数后传送给目前的进程。如果参数 seconds 为 0 ,则之前设置的闹钟会被取消,并将剩下的时间返回。
  • 返回值 : 返回之前闹钟的剩余秒数 ,如果之前未设闹钟则返回 0
  • alarm() 执行后,进程将继续执行,在后期 (alarm 以后)的执行过程中将会在 seconds 秒后收到信号 SIGALRM 并执行其处理函数。

sleep()

  • unsigned int sleep(unsigned int seconds);
  • sleep() 是在库函数中实现的,它是通过调用 alarm() 来设定报警时间, 调用sigsuspend将进程挂起

usleep()

  • unsigned int usleep (unsigned int useconds
  • usleep 的时间单位为 us ,肯定不是由 alarm 实现的,但都是 linux用的,而 window 下不能用,因为都是 sleep 和 usleep 都是在unistd.h 下定义的。
  • 可能被其他信号打断。
  • return :若进程暂停到参数 seconds 所指定的时间,成功则返回 0;若有信号中断则返回剩余微秒数 。

setitimer()

int setitimer (int which, const struct itimerval *value, struct itimerval ovalue));
struct itimerval {
    struct timeval it_interval ; // next value
    struct timeval it_value ; // current value
};
struct timeval {
    long tv_sec ; //seconds 时间的秒数
    long tv_usec ; //micro seconds 时间的微秒数
}
  • which 可选项

    • ITIMER_REAL : 以 系统真实的时间 来计算,它送出 SIGALRM 信号。
    • ITIMER_VIRTUAL : 以该进程在 用户态下花费的时间 来计算,它送出SIGVTALRM 信号。
    • ITIMER_PROF : 以该进程 在用户态下和内核态下所费的时间来计算,它送出 SIGPROF 信号。
  • setitimer 调用成功返回 0 ,否则返回 1

  • 例子

    #include 
    #include 
    #include 
    #include 
    
    int i = 0;
    
    void timeChange(int ms, struct timeval *ptVal) {
        ptVal->tv_sec = ms / 1000;
        ptVal->tv_usec = (ms % 1000) * 1000;
    }
    
    void alarmsign_handler(int SignNo){
        printf("%d seconds\n", ++i);
    }
    
    int main(){
        struct itimerval tval;
        signal(SIGALRM, alarmsign_handler);
        timeChange(1, &(tval.it_value));
        timeChange(1000, &(tval.it_interval));
        setitimer(ITIMER_REAL, &tval, NULL);
        while(getchar() != '#');
        return 0;
    }
    
  • 结果

    Linux 信号的处理、含义、发送和定时信号_第4张图片

你可能感兴趣的:(linux,网络,unix)