LinuxC—信号

LinuxC语言信号的基本概念与基本使用 Linux环境下,信号是一种用于通知进程发生了某种事件的机制。这些事件可能是由操作系统、其他进程或进程本身触发的。对于C语言编程者来说,理解信号的基本概念和使用方法是非常重要的,因为它们可以用来实现进程间通信、处理异常情况以及实现一些高级功能。通过学习信号的基本概念和使用方法,我们可以更好地编写可靠的Linux应用程序。通过注册信号处理函数,我们可以定义在收到特定信号时所采取的行动,从而实现进程的自定义行为。 阅读详情

信号

1 信号的概念

  • 信号是软件层面的中断
  • 信号的响应依赖于中断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xv72jW1m-1673427658676)(…/牛客项目/temp/image-20230109101013452.png)]

2 signal(2)

定义——信号处理函数,注册当前信号的行为

当signum信号到来了,执行handler行为,最终返回这个信号之前的行为

#include <signal.h>
typedef void (*sighandler_t)(int);//这个int就是信号的标识
sighandler_t signal(int signum, sighandler_t handler);

标准形式:因为C中可能有重名的风险

void (*singal(int signum, void (*func)(int)))(int);

示例1

int main(int argc, char **argv) {
   
   

    int i;
    //SIGINT表示终端中断,shell中的ctrl+c
    //SIG_IGN表示忽略信号
    signal(SIGINT, SIG_IGN);
    for (i = 0; i < 10; i++) {
   
   
        write(1, "*", 1);
        sleep(1);
    }
    exit(0);
}

输出结果:可以看到即使ctrl+c了依旧在接着输出,说明终止信号被忽略了,还有一点signal是给程序规定了接收到信号后的动作,在规定之后程序运行过程中只要接受到有关信号就会做出响应

**^C********

示例2

static void int_handler(int s) {
   
   
    write(1, "!", 1);
}

int main(int argc, char **argv) {
   
   

    int i;
    signal(SIGINT, int_handler);
    for (i = 0; i < 10; i++) {
   
   
        write(1, "*", 1);
        sleep(1);
    }
    exit(0);
}

输出结果:可以看到在接受到信号的时候执行了int_handler

***^C!***^C!***^C!*

注意,当我们非常快的按ctrl+c时,可以看到程序非常快就就结束了,这是因为信号会打断阻塞的系统调用,对于这个现象,比如open(), read(), write()系统调用都有可能被信号打断,当其被信号打断时会返回EINTR(<0的数),所以需要对其返回值做判断,是EINTR时需要继续执行我们的程序。

3 可重入函数

信号的不可靠

信号处理函数的执行现场是内核布置的,当多个信号连续到来时就可能出现一个问题,后面信号的现场给前面的信号处理现场覆盖掉了,这就是信号的不可靠,要解决这种问题就引入了可重入函数

可重入函数

  • 重入的概念:第一次调用还没结束就开始第二次调用,可能导致第一次调用出现一次不可预料的结果

  • 所有的系统调用都是可重入的,一部分标准库函数也是可重入的,比如一些函数会有_r的版本,其中没有_r版本的就不能够用在信号处理函数当中,防止重入的现象,localtime返回的tm*所指向的内存是静态区,如果出现重入现象就会导致原先的tm结果被覆盖,所以引入_r版本,由调用的用户来决定执行结果的存放区域即result

    struct tm *localtime(const time_t *timep);
    struct tm *localtime_r(const time_t *timep, struct tm *result);
    

    又比如memcpy函数,是可重入的

4 信号的响应过程

具体过程

首先的明白一个概念,就是调度,虽然宏观上看着好像我们某个程序一致占用着CPU在进行,但实际上在微观上程序实际上是交替着执行,只不过因为交替的非常快,导致我们看到的效果好像一个程序一致占用着CPU在执行,这个交替的策略,就是所谓的调度策略,现代CPU的调度策略通常都是时间片调度,就是说规定一个CPU时间片,某个进程CPU调度后只能执行这样的一个时间片的时间,执行完后就会响应时钟中断,讲这个进程切换到内核态后进入队列中等待下一次被CPU调度。理解了这个概念后,再来看信号响应的过程,以4.2中的函数为例:

  • main函数负责打印*
  • int_handler信号处理函数负责打印!

内核为每个进程维护了两个和信号有关的位图,分别是:

  • mask位图,信号屏蔽字,用来表示当前的信号的状态
  • pending位图,用来记录当前进程接收到了哪些信号

这两个位图中的每一位就对应了一种信号,当进程接受到一个信号的时候就会将信号对应的pending位置1,这时进程可能正在被调度或者已经在调度队列里面等着了,这里需要注意在从user态到kernel态时(该过程就是时间片用完,响应CPU调度了)进程会在自己内核中的PCB(进程控制块)中保存好自己的执行现场,比如就有执行的下一条指令的地址address。

当进程再次被CPU调度的时候,也就是要从kernel态切换到user态执行时,会将mask和pending做一个与运算,这时pending位被置1的位就会被保留,这时候就知道进程接受到哪些信号了,这时,内核会将PCB中的address换成信号处理函数的入口地址后执行信号处理函数,同时将该信号在mask和pending位图中对应的位置置0,在执行完信号处理函数(打印!)以后又会回到kernel态将address换成原先main执行到的指令地址,并将mask中对应的位置1,之后再被调度时又会经历kernel态到user态,又会做mask&pending重复上面的过程。

需要注意的点

  • 信号从收到到响应有一个不可避免的延迟,这很好理解,从上面的解释可以看出,响应信号是依赖于时钟中断的,比如我们进程在内核队列中等待被调度时,可能就接受到一个信号,但这个信号的响应必须等到该进程下一次被调度经历kernel态到user态的转换时才会进行
  • 标准信号的响应没有严格的顺序
  • 如何忽略一个信号:将该信号对应的mask位置0
  • 标准信号是会丢失的,因为标准信号用的是位图,当我们在执行一个信号处理函数时,无论这个进程又接受到多少信号都只会重复将相应的pending位置1的动作,并不会计数
  • 不能从信号处理函数中随意地往外跳转(setjmp()和longjmp()),若跳转可能导致mask对应位不再被置1从而导致该信号以后再也不会被响应。(但是sigsetjmp()和siglongjmp()可以用来跳转)

5 常用函数

5.1 函数定义

kill(2) 给pid进程发送sig信号

  • 定义
/* Send signal SIG to process number PID.  If PID is zero,
   send SIG to all processes in the current process's process group.
   If PID is < -1, send SIG to all processes in process group - PID.  */
extern int kill (__pid_t __pid, int __sig) __THROW;
  • pid的选择
    • pid > 0,就是给pid对应的进程发信号
    • pid =0,给调用该方法的进程的同组进程发送sig信号,即组内广播
    • pid = -1 ,sig会被发送给当前进程能够发送信号的所有进程,除了init进程
    • pid < -1,会将sig发送给进程组ID为-pid的进程
  • sig = 0时为检测pid进程或-pid进程组是否存在
  • 返回值:
    • 成功返回0
    • 失败返回-1,同时设置errno

raise(3) 给当前进程发送一个信号,自己给自己发

  • 定义
/* Raise signal SIG, i.e., send SIG to yourself.  */
extern int raise (int __sig) __THROW;
  • 返回值:0表示成功,非0表示失败

alarm(2) 在seconds秒后给进程发送一个SIGALRM信号

/* Schedule an alarm.  In SECONDS seconds, the process will get a SIGALRM.
   If SECONDS is zero, any currently scheduled alarm will be cancelled.
   The function returns the number of seconds remaining until the last
   alarm scheduled would have signaled, or zero if there wasn't one.
   There is no return value to indicate an error, but you can set `errno'
   to 0 and check its value after calling `alarm', and this might tell you.
   The signal may come late due to processor scheduling.  */
extern unsigned int alarm (unsigned int __seconds) __THROW;

注意,当连续使用多个alarm()时会生效最后一个

pause(2)

​ 等待直到有一个信号来打断

/* Suspend the process until a signal arrives.
   This always returns -1 and sets `errno' to EINTR.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int pause (void);

sleep(3)

​ 阻塞进程seconds后唤醒进程,或在sleep途中被信号打断,该函数轻易不能使用,因为在有些环境下该函数就是用alarm() + pause()来封装的,当程序中有多个alarm时是可能会出错的

/* Make the process sleep for SECONDS seconds, or until a signal arrives
   and is not ignored.  The function returns the number of seconds less
   than SECONDS which it actually slept (thus zero if it slept the full time).
   If a signal handler does a `longjmp' or modifies the handling of the
   SIGALRM signal while inside `sleep' call, the handling of the SIGALRM
   signal afterwards is undefined.  There is no return value to indicate
   error, but if `sleep' returns SECONDS, it probably didn't work.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern unsigned int sleep (unsigned int __seconds);

可以用nanosleep()/usleep()/select()函数来代替sleep()

setitimer(2) 设置一个时钟

设置一个时钟,当old不会空时将原来时钟的信息回填到old中,将new中的时间都设置为0即可取消定时器

/* Set the timer WHICH to *NEW.  If OLD is not NULL,
   set *OLD to the old value of timer WHICH.
   Returns 0 on success, -1 on errors.  */
extern int setitimer (__itimer_which_t __which,
            const struct itimerval *__restrict __new,
            struct itimerval *__restrict __old) __THROW;
  • which表示哪个时钟,共有三种

    • ITIMER_REAL 实时(realtime)递减,直到为0时发送一个SIGALRM信号,此时可以代替alarm()函数
    • ITIMER_VIRTUAL 当进程运行时(usertime)递减,到0时发送一个SIGVTALRM信号
    • ITIMER_PROF 递减usertime+kerneltime,到0时发送一个SIGPROF信号
  • itimerval结构体

struct itimerval {
   
   
    struct timeval it_interval; /* 递减到0时将这个值赋给value且操作时原子的,即该函数自己就能构成一个										时钟周期*/
    struct timeval it_value;    /* 递减的就是这个值*/
};

 struct timeval {
   
   
     time_t      tv_sec;         /* seconds */
     suseconds_t tv_usec;        /* microseconds 微秒*/
 };
  • 返回值:0表示成功,-1表示失败并设置errno的值

abort(3)

给当前进程发送一个SIGABRT,目的是为了结束当前进程

/* Abort execution and generate a core-dump.  */
extern void abort (void) __THROW __attribute__ ((__noreturn__));

system()

在可能有信号干扰的情况下使用这个函数需要注意将SIGCHLD信号block,并将SIGINT和SIGQUIT信号忽略

5.2 函数计时比较

计算5s中能将一个数++多少次

  • 使用time函数来实现
int main(int argc, char **argv) {
   
   

    time_t end;
    end = time(NULL) + 5;
    int64_t cnt = 0;

    while (time(NULL) <= end) cnt++;

    printf("%ld\n", cnt);

    exit(0);
}

​ 输出结果:

root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main
2062958242

real	0m5.401s
user	0m5.400s
sys	0m0.000s
  • 使用alarm+信号来实现
int main(int argc, char **argv) {
   
   

    alarm(5);
    int64_t cnt = 0;

    while (1) {
   
   
        cnt++;
    }

    exit(0);
}

​ 输出结果

root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main
2859820977
real	0m5.001s
user	0m4.997s
sys	0m0.004s

可以看到使用信号来实现时时间更加精确,同时cnt++执行的次数也更多了

static volatile int loop = 1;

static void alarm_handler(int s) {
   
   
        loop = 0;
}

int main(int argc, char **argv) {
   
   
    //注意signal得再alarm前面
	signal(SIGALRM, alarm_handler);
    int64_t cnt = 0;
    alarm(5);
    
    
    while (loop) {
   
   
        cnt++;
    }
    printf("%ld", cnt);

    exit(0);
}

5.3 漏桶实现

实现一个shell中的cat命令,要求每秒只能读取10个字符

#define CPS 10
#define BUFSIZE CPS

static volatile int loop = 0; //控制时延的标志

static void alrm_handler(int s) {
   
   
    alarm(1);
    loop = 1;
}

/**
 * 实现一个cat,且每秒读取CPS个字符
 */
int main(int argc, char **argv) {
   
   

    if (argc < 2) {
   
   
        fprintf(stderr, "Usage...\n");
        exit(1);
    }

    int fds, fdd = 1, len, ret, pos;
    char buf[BUFSIZE];

    //打开待读取的文件
    do {
   
   
        fds = open(argv[1], O_RDONLY);
        if (fds < 0) {
   
   
            if (errno == EINTR) continue;
            perror("open():");
            exit(1);
        }
    } while (fds < 0);

    //向终端上写数据
    //首先注册信号函数
    alarm(1);
    signal(SIGALRM, alrm_handler);

    while (1) {
   
   
        while (!loop) pause();
        loop = 0;
        while ((len = read(fds, buf, BUFSIZE)) 
Linux系统编程(信号处理机制) 本篇文章我们来讲解信号的处理机制,信号处理在Linux操作系统中必不可少,这一点值得大家注意,信号又会与中断,异常一起讨论,那么下面我们就来看看到底什么是信号吧。本篇文章就讲解到这里,大家看完后可以好好实验一下。 阅读详情

相关推荐

LinuxC语言信号集与sigprocmask

Linux C编程中,信号是一种重要的进程间通信机制,用于通知进程发生了特定的事件。然而,程序在执行过程中可能会收到各种各样的信号,有些信号可能会在不合适的时候打断程序的正常执行,造成不可预料的后果。为了控制信号的接收和处理,Linux提供了信号集和sigprocmask()函数。信号集是一种数据结构,用于管理信号的集合,而sigprocmask()函数则允许程序员临时修改进程的信号屏蔽字,以控制信号的接收和处理。本文介绍了Linux C编程中的信号集与sigprocmask()函数。

m0_62599305的博客 1234

LinuxC信号

linux信号相关接口

未来的路 1129

LinuxC语言】POSIX信号

在并发编程中,我们经常需要对共享资源进行访问控制,以防止数据竞争和不一致性。在Linux C语言中,我们使用一种称为“信号量”的机制来实现这种控制。信号量是一种同步工具,用于保护临界区域和实现进程间的同步。总的来说,信号量在Linux C语言中是一种强大的同步工具,它们提供了一种有效的方式来保护临界区域,防止数据竞争和不一致性。理解和正确使用信号量对于编写高效、可靠的并发程序至关重要。通过本文,我们希望你对Linux C语言中的信号量有了更深入的理解,并能在你的编程实践中找到它们的应用。

m0_62599305的博客 368

[Linux C] 信号signal 的使用

信号的创建有两套api,一个是signal,一个是sigaction,signal缺陷很多,比如没有提供触发后自动再次设置处理信号处理策略,这会导致连续触发的两个信号,一个进入了期待的信号处理流程,另外一个则进入了默认的信号处理流程。但是信号的递送可能会出现阻塞,这个阻塞发生在信号发送者把信号送入内核的信号队列中(需要从代码层面验证)。signal 是一种通信机制,可以跨进程发送,可以同进程跨线程发送,可以不同进程向指定线程发送。

ykun089的博客 757

Linux 信号详解

信号共性: 简单、不能携带大量信息、满足条件才发送。 信号的特质: 信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。 所有信号的产生及处理全部都是由【内核】完成的。 信号相关的概念: 产生信号: 1. 按键产生 2. 系统调用产生 3. 软件条件产生 4. 硬件异常产生 5. 命令产生 概念: ...

一颗牙疼 334

Linux【C编程】 信号以及信号的处理方式

Linux中的信号的详解,以及在进程中如何处理信号

weixin_46999174的博客 1812

LinuxC 信号

信号信号处理 一、信号概念 1.信号编号 kill -l ps aux ps ajx linux中提供了64种信号编号,前32为经典信号(软件/操作系统),后32为实时信号(硬件/驱动)。 2.信号机制 man 7 signal 3.信号产生种类 1.特殊终端按键 ctl+c - SIGINT ctl+z - SIGTSTP(暂停) ctl+\ - SIGQUIT 2.硬件异常 除以0操作 访问非法内存 3.kill函数或kill命令 int kill(pid_t pid,int sig) pid

m0_53313553的博客 312

Linuxc编程之信号

写这篇博客只是在梳理这几天我看到知识。 LinuxC编程比较难,以前没有接触过操作系统理论,不是正统的计算机专业出身。看书看着看着就忘了,这是我第2次看《LinuxC编程一站式学习》。看第一遍的时候,思维混乱,过目就忘。 所以这回,我借写博客,边看边整理知识体系,以便能真正的记住。 信号中的两个概念: 1、信号递达(Delivery):执行信号的处理叫信号递达。 2、信号未决(P

qq_17461615的专栏 309

Linuxc/c++之信号基础

Linux c之信号的基础使用

ShiXiAoLaNga的博客 2577

LinuxC编程】05 - 信号

【黑马程序员-Linux系统编程】https://www.bilibili.com/video/BV1KE411q7ee?

u014680146的博客 801

LinuxC编程实战信号详细总结

LinuxC编程实战信号总结信号的来源信号的种类1.可靠信号和不可靠信号2.信号的优先级信号的捕捉和处理信号处理函数的返回 本文参考《LinuxC编程实战 信号的来源 信号来源按产生体哦啊间的不同分为硬件和软件两种方式。 1.硬件方式: 1). 用户按下某些键所产生的信号。如<ctrl + C>组合键产生一个SIGINT信号。 2). 硬件异常产生的一些信号。 2.软件方式: 1). 用户在终端下用kill命令向进程发送任意信号; 2). 进程调用kill 或 sigqueue 函数发送信号;

weixin_53012351的博客 429

Linuxc信号量集合

源代码:receive.c#include &lt;stdio.h&gt;#include &lt;stdlib.h&gt;#include &lt;unistd.h&gt;#include &lt;sys/ipc.h&gt;#include &lt;sys/types.h&gt;#include &lt;sys/sem.h&gt;int main(){ int semid,semval1,sem...

qq_37192076的博客 865

LinuxC进程——信号(Signal)

信号共性: 简单、不能携带大量信息、满足条件才发送。 信号的特质: 信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。 所有信号的产生及处理全部都是由【内核】完成的。 信号相关的概念: 产生信号: 1. 按键产生 2. 系统调用产生 3. 软件条件产生 4. 硬件异常产生 5. 命令产生 概...

m0_50814851的博客 670

Linuxc 信号的使用4

1.进程A向进程B发送信号;2.进程B收到进程A发送的信号后,打印出发送信号进程的pid,uid以及信号值。源代码:send.c#include &lt;stdio.h&gt;#include &lt;stdlib.h&gt;#include &lt;sys/types.h&gt;#include &lt;signal.h&gt;#include &lt;unistd.h&gt;int main(...

qq_37192076的博客 469

linux c signal 函数,Linux c之信号集使用

Linux信号集1.信号集概念信号集是一个能表示多个信号的数据类型,sigset_t set ;set即一个信号集。既然是一个集合,就需要对集合进行添加/删除等操作。int sigemptyset(sigset_t *set); 将set集合置空int sigfillset(sigset_t *set); 将所有信号加入set集合int sigaddset(sigset_t *set,int s...

weixin_36339710的博客 148

LinuxC信号信号处理(1)

LinuxC信号信号处理(1)

YinJianxiang的博客 1033

Linuxc 信号的发送与接收

信号发送函数kill(), sigqueue(), raise(), alarm(), setitimer(), pause(),abort()信号安装函数signal(), sigaction()信号集操作函数sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember()注:kill  signal  sigqueue  ...

qq_37192076的博客 2002

Linux C编程实战》笔记:信号的屏蔽

在的sigaction的sa_mask成员,它的类型就是一个信号集,下面我们来介绍它。

ouliten的博客 1208
上一篇: LinuxC—进程
下一篇: LinuxC—线程
o今晚打老虎o
博客等级 码龄6年 7粉丝 67原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值