【C语言】信号

简介: 【C语言】信号

信号

1. 信号状态

信号有三种状态:产生、未决和递达

信号产生方式:

  • 按键产生,ctrl+c 产生 中断信号SIGINT,ctrl + \ 产生退出信号 SIGQUIT并生成core文件,ctrl +z产生停止信号SIGSTOP
  • 系统调用,例如kill、raise、abort函数
  • 定时器
  • 发生异常

信号未决状态:

​ 是指信号在阻塞信号集中被设置为阻塞,那么接收到这个信号后,会被保存到进程的未决信号集中,当阻塞解除才能执行对应信号处理函数。

信号递达状态:

​ 进程接收到信号并执行了对应操作。

2. 信号处理方式

  • 执行默认的操作
  • 忽略
  • 捕获,执行用户定义的处理函数

信号无法排队,所以如果同时来了很多信号,那么需要用循环进行处理

查看当前系统有哪些信号 kill -l ,其中 SIGKILL 和 SIGSTOP 不能被捕捉,阻塞,或忽略。

 1) SIGHUP         2) SIGINT     3) SIGQUIT         4) SIGILL     5) SIGTRAP
 6) SIGABRT         7) SIGBUS     8) SIGFPE         9) SIGKILL    10) SIGUSR1
11) SIGSEGV        12) SIGUSR2    13) SIGPIPE        14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT        19) SIGSTOP    20) SIGTSTP
21) SIGTTIN        22) SIGTTOU    23) SIGURG        24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS

3. 信号注册相关函数

//signal()
//用于注册信号捕捉函数,当捕获对应信号时,执行相应函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数
    signum: 信号
    handler: 信号处理函数

//sigaction()
//用于注册信号捕捉函数,执行相应函数,并且添加处理方式,返回0成功,返回-1失败
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
   
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

参数
    signum: 捕捉的信号
    act:     传入参数,新的信号处理方式
        sa_handler:信号处理函数,还可赋值为SIG_IGN表示忽略信号到来 或 SIG_DFL表执行默认动作
        sa_sigaction:如果在 sa_flags 中指定了 SA_SIGINFO ,则 sa_sigaction(而不是SA_handler)作为signum的信号处理函数。此函数接收的信号号作为其第一个参数,一个指向 siginfo_t 类型的指针作为其第二个参数,这个类型结构体包含了很多和进程相关的信息,以及一个指向ucontext_t类型(强制转换为void*)的指针作为第三个参数。(通常,处理程序函数不使用第三个参数。),通常设置为NULL
        sa_mask:信号处理时需要阻塞的信号掩码
        sa_flags:通常为0,表示使用默认标识
        sa_restorer:不应用于应用程序,已舍弃
    oldact :传出参数,旧的信号处理方式
        同上

//kill()
//用于向某个进程发信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数
    pid 进程id
    sig 信号

//raise()
//用于自己给自己发送信号
#include <signal.h>
int raise(int sig);
返回值
    成功返回0
    失败返回非0

//abort()
//用于自己给自己发送 SIGABRT 信号,等价于 abort() == kill(getpid(), SIGABRT);
#include <stdlib.h>
void abort(void);

//alarm()
//设置时钟seconds秒数,内核会给当前进程发送 SIGALRM 信号,每个进程只有一个定时器
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
返回值
    返回0或者剩余秒数
//取消定时器使用alarm(0),返回旧闹钟余下秒数,不管进程处于什么状态,计时器一直计时

//setitimer()
//也是设置计时器,比alarm函数计时得更精确,可以精确到微秒。子进程不会继承父进程的计时器
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
              struct itimerval *old_value);

struct itimerval {
   
    struct timeval it_interval; /* Interval for periodic timer */计时周期时间
    struct timeval it_value;    /* Time until next expiration */第一次计时时间,可通过getitimer函数获取该值得到剩余计时时间
};

struct timeval {
   
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};
//成功返回0,失败返回-1
参数
    which: 指定定时方式
        ITIMER_REAL        计算实时时间,给当前进程发送 SIGALRM 信号
        ITIMER_VIRTUAL    计算进程运行时间,发送 SIGVTALRM 信号
        ITIMER_PROF        计算进程运行时间和系统调用时间,这个通常和ITIMER_VIRTUAL组合进行评测进程运行时间和系统调用时间,发送 SIGPROF 信号。

getitimer 中 curr_value 是传出参数,表示剩余时间。
setitimer 中 
        new_value 设置计时时间
        old_value 通常设置为NULL,如果非NULL,那么是一个传出参数,返回的是原来的计时器。

示例:

创建三个子进程,子进程结束时会给父进程发送 SIGCHLD 信号,父进程执行对应函数回收子进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void sighandle(int signo)
{
   
    pid_t pid;
    while (1)
    {
   
        pid = waitpid(-1, NULL, WNOHANG);
        if (pid > 0)
        {
   
            printf("child [%d] is over\n", pid);
        }
        else if (pid == 0)
        {
   
            printf("no more child need clean");
            break;
        }
        else if (pid == -1)
        {
   
            printf("no child is living, pid==[%d]\n", pid);
            break;
        }
        else
        {
   
            perror("waitpid error");
            break;
        }
    }
}

int main()
{
   
    int i = 0;
    pid_t pid;
    signal(SIGCHLD, sighandle);
    for (i = 0; i < 3; i++)
    {
   
        pid = fork();
        if (pid < 0)
        {
   
            perror("fork error");
            return -1;
        }
        else if (pid > 0)
        {
   
            // father
        }
        else
        {
   
            // child
            printf("here is child [%d],father is [%d]\n", getpid(), getppid());
            break;
        }
    }

    if (i==3)
    {
   
        while (1)
        {
   
            sleep(1);
        }
    }

    return 0;
}

进程实际执行时间 = 系统时间 + 用户时间 + 损耗时间

损耗的时间主要来来自文件IO操作,IO操作会有用户区到内核区的切换,切换的次数越多越耗时。

4. 信号集相关函数

信号集有阻塞信号集和未决信号集,当某些信号在阻塞信号集中被设置为阻塞,那么当这个信号来时,该进程的未决信号集会将该信号对应设置为未决信号。只有解除该信号为非阻塞,才会执行对应的信号处理函数。

#include <signal.h>
//初始化信号集,清空所有信号
int sigemptyset(sigset_t *set);
//初始化信号集,包含所有信号
int sigfillset(sigset_t *set);
//添加信号进入到某个信号集
int sigaddset(sigset_t *set, int signum);
//从信号集中删除某个信号
int sigdelset(sigset_t *set, int signum);
//上面都是返回0成功,返回-1失败
//判断信号是否在这个信号集中,返回 1在,0不在,-1错误
int sigismember(const sigset_t *set, int signum);

//sigpending()
//获取当前进程的未决信号集,set为传出参数,
int sigpending(sigset_t *set);

//sigprocmask()
//用来修改进程pcb中阻塞信号集中的内容,成功返回0,失败返回-1
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
    how:将后面的set进行某种操作
        SIG_BLOCK    :将set添加到阻塞信号集
        SIG_UNBLOCK    :将set从阻塞信号集移除
        SIG_SETMASK    :直接将set作为阻塞信号集
    set: 信号集,可以使用上面的一系列函数得到
    oldset: 通常设为NULL,否则返回之前的阻塞信号集

示例:

创建三个子进程,并且屏蔽SIGINT中断信号,然后注册SIGCHLD信号处理函数,注册时阻塞SIGCHLD信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

void sighandle(int signo)
{
   
    pid_t pid;
    while (1)
    {
   
        pid = waitpid(-1, NULL, WNOHANG);
        if (pid > 0)
        {
   
            printf("child [%d] is over\n", pid);
        }
        else if (pid == 0)
        {
   
            printf("no more child need clean\n");
            break;
        }
        else if (pid == -1)
        {
   
            printf("no child is living, pid==[%d]\n", pid);
            break;
        }
        else
        {
   
            perror("waitpid error");
            break;
        }
    }
}

int main()
{
   
    int i = 0;
    pid_t pid;
    char usig = SIGINT;
    // signal(SIGCHLD, sighandle);
    sigset_t set;
    sigset_t oldset;

    int ret = sigemptyset(&set);
    if (ret < 0)
    {
   
        perror("sigemptyset error");
        return -1;
    }
    ret = sigaddset(&set, usig);
    if (ret < 0)
    {
   
        perror("sigaddset error");
        return -1;
    }
    ret = sigismember(&set, usig);
    if (ret < 0)
    {
   
        perror("sigaddset error");
        return -1;
    }
    else if (ret == 0)
    {
   
        printf("error:usig not in set\n");
        return -1;
    }
    else if (ret == 1)
    {
   
        printf("usig in set\n");
    }
    //添加信号SIGINT到阻塞信号集
    ret = sigprocmask(SIG_BLOCK, &set, &oldset);
    if (ret < 0)
    {
   
        perror("sigprocmask error");
        return -1;
    }

    for (i = 0; i < 3; i++)
    {
   
        pid = fork();
        if (pid < 0)
        {
   
            perror("fork error");
            return -1;
        }
        else if (pid > 0)
        {
   
            // father
        }
        else
        {
   
            // child
            printf("here is child [%d],father is [%d]\n", getpid(), getppid());
            break;
        }
    }

    if (i == 3)
    {
   
        //注册捕捉函数
        struct sigaction act;
        memset(&act, 0x00, sizeof(act));
        ret = sigemptyset(&act.sa_mask);
        ret = sigaddset(&act.sa_mask, SIGCHLD);
        act.sa_handler = sighandle;
        act.sa_flags = 0;

        ret = sigaction(SIGCHLD, &act, NULL);
        if (ret < 0)
        {
   
            perror("sigaction error");
            return -1;
        }
        printf("here is father last words\n");
        while (1)
        {
   
        }
    }

    return 0;
}

//输出
usig in set
here is father last words
here is child [2751],father is [2750]
child [2751] is over
no more child need clean
here is child [2752],father is [2750]
child [2752] is over
no more child need clean
here is child [2753],father is [2750]
child [2753] is over
no child is living, pid==[-1]

示例:

使用定时器,定时发送计时结束信号SIGALRM

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

void sighandle(int signo)
{
   

    printf("signo is [%d]\n", signo);
}

int main()
{
   
    // alarm(3);
    // int getitimer(int which, struct itimerval *curr_value);
    // int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
    struct itimerval time;
    struct itimerval lasttimer;
    memset(&time,0x00,sizeof(time));
    memset(&time,0x00,sizeof(lasttimer));
    time.it_interval.tv_sec = 3;
    time.it_interval.tv_usec = 0;
    time.it_value.tv_sec = 1;
    time.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &time, NULL);
    int ret;
    // 注册捕捉函数
    struct sigaction act;
    memset(&act, 0x00, sizeof(act));
    ret = sigemptyset(&act.sa_mask);
    // 信号处理的时候阻塞SIGALRM信号
    ret = sigaddset(&act.sa_mask, SIGALRM);
    act.sa_handler = sighandle;
    act.sa_flags = 0;

    ret = sigaction(SIGALRM, &act, NULL);
    while (1)
    {
   
        sleep(1);
        getitimer(ITIMER_REAL,&lasttimer);
        printf("[%ld:%ld]\n",lasttimer.it_value.tv_sec,lasttimer.it_value.tv_usec);
    }

    return 0;
}
//输出
signo is [14]
[2:978958]
[1:955765]
[0:950218]
signo is [14]
[2:995498]
[1:984102]
[0:970387]
signo is [14]
[2:978756]
[1:964049]
[0:915772]
signo is [14]
...
目录
相关文章
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
833 23
|
7月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1420 0
|
9月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
430 15
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
594 1
一文彻底搞清楚C语言的函数
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
772 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
347 24
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
752 16
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
655 3
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
741 2