经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
Linux C 信号
来源:cnblogs  作者:熊沐  时间:2021/6/7 9:15:00  对本文有异议

信号

总结自Unix手册第20 21 22章

信号产生的过程:信号因某事件而产生,稍后(信号的产生和传递之间存在时间间隔,这个时间间隔可能是因为进程正在执行某个系统调用,因此在这个系统调用返回前,信号不会被传递,此时信号处于等待(pending状态)被传递至指定进程,进程接收信号后作出响应。

基础和概念

信号处置

信号处理器

信号处理器:信号被捕获时调用的函数,该函数由内核代表进程进行调用,保证可以随时打断接收信号的进程。信号处理器的设计应该力求简单。信号处理器形如

  1. void handler(int sig)
  2. {
  3. }

传入信号的编号,处理器可以根据信号种类的不同选择性的执行一些代码,也就是说,一个信号处理器可以用来处理多种不同的信号。

改变信号处置:signal()

signal函数不如sigaction函数优秀,前者在不同UNIX实现中存在差异,但后者使用更加复杂(功能也更强大)

  1. #include <signal.h>
  2. sig_t signal(int sig, sig_t handler)
  • sig希望改变处理行为的信号编号
  • handler指明改变后信号处理函数,一般这个函数具有这样的形式

改变信号处置:sigaction()

  1. int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
  • sig信号
  • const struct sigaction *act:是一个指针,指向描述信号新处置方式的数据结构,具体使用见下
  • struct sigaction *oldact:返回之前信号处置的信息

struct sigaction

  1. struct sigaction
  2. {
  3. union
  4. {
  5. void (*sa_handler)(int);
  6. void (*sa_sigaction)(int, siginfo_t * , void * );
  7. } sigaction_handler;
  8. // 信号的集合,用于记录被阻塞的信号 需要用特定函数处理
  9. sigset_t sa_mask;
  10. // 指明信号处理的行为
  11. int sa_flags;
  12. void (*sa_restorer) (void);
  13. };
  • sigaction_handler:(匿名联合体,存在于结构体或联合体中,使用时不需要通过联合体名,可以直接使用)
    • sa_handler为函数指针,对应signal()中的handler函数
      • 可以指向SIG_DELSIG_IGN常量之一
      • 可以自定信号处理器
        • 只有使用自定义函数,sa_masksa_flags的设置才有意义
    • sa_sigaction函数指针,可以完成复杂的信号工作
  • sa_mask用于设定信号处理器执行时阻塞的信号。
    • 使用细节是:内核调用信号处理器之前,将sa_mask中设定掩码的信号添加到进程的信号掩码中,直至信号处理器函数返回,再从进程的信号掩码中移除之前添加的信号。
  • sa_flags是位掩码,设定需要使用|
    • SA_SIGINFO
      • 发送信号时发送附加信息,信号处理器要声明成void handler(int sig, siginfo_t *info, void *context);
  • sa_restorer没看到如何用,空下

信号信息的携带:siginfo_t

一个非常复杂的结构体,未了解。

父子信号处理

父进程创建子进程,子进程继承父进程信号处理方式,直到子进程调用exec函数。exec函数将调用者的信号处理方式还原成默认。

信号发送

发送信号:kill()

不是去扼杀进程,而是只发送信号,只是早期UNIX实现中大多数信号的功能是终止进程。

  1. #include <signal.h>
  2. int kill(pid_t pid, int sig);
  • pid标识一个或多个目标进程
    • pid > 0:pid为指定进程
    • pid == 0:pid发送信号给发送信号所在的进程同组的每个进程
      • 也就是所有子进程
    • pid < -1:向组ID为-pid的进程组内所有下属进程
    • pid == -1:调用进程有权发送的所有进程(如果使用ssh连接服务器实验,执行后发现ssh断了)
信号发送的权限

发送信号必须满足发送信号的进程和接收信号的进程的用户ID相同,或者是发送信号的进程的用户是root。

举例
  1. // 这个是发送信号的代码
  2. #include <sys/types.h>
  3. #include <signal.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. int main(int argc, char **argv)
  7. {
  8. int result, i;
  9. if(argc > 1)
  10. {
  11. result = kill(atoi(argv[1]), SIGINT);
  12. printf("result = %d\n", result);
  13. }
  14. return 0;
  15. }
  1. // 这个是接收信号的代码
  2. // 只是一个耗时间的计算,注意不能用sleep代替,因为sleep会使进程挂起
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. printf("%d\n", getpid());// 输出此进程的pid
  8. int i = 0;
  9. double x = 5, y = 0.9548, e = 2.7;
  10. for(i = 0; i < 500000000; i++)
  11. {
  12. x = x * e + (x - i) * e - 3.65 * (y - e);
  13. y = y * (x - e);
  14. x -= y;
  15. }
  16. printf("%f\n", x);
  17. return 0;
  18. }
  1. // 测试kill(0, SIGINT);
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <unistd.h>
  6. void handler(int sig);
  7. int main(int argc, char **argv)
  8. {
  9. signal(SIGINT, handler);
  10. pid_t father = getpid(), son[10];
  11. for (size_t i = 0; i < 3; i++)
  12. {
  13. if (getpid() == father)
  14. {
  15. son[i] = fork();
  16. if (son[i] != 0)
  17. {
  18. printf("when %ld : %d -> %d\n", i, father, son[i]);
  19. }
  20. else
  21. {
  22. sleep(2);// 子进程休眠,等待父进程发送信号
  23. printf("%d is safe\n", getpid());
  24. }
  25. }
  26. }
  27. if (getpid() == father)
  28. {
  29. sleep(1); // 如果不加这句话,可能子进程被创建但没来得及执行就被kill了信息
  30. kill(0, SIGINT);
  31. printf("%d is safe\n", getpid());
  32. sleep(2);
  33. }
  34. return 0;
  35. }
  36. void handler(int sig)
  37. {
  38. printf("%d use handler SIGINT\n", getpid());
  39. }

向自己发送信号:raise()

  1. int raise(int sig);
  • 对于单线程:相当于kill(getpid(), sig);
  • 对于非单线程:相当于pthread_kill(pthread_self(), sig);

由于信号的处理有内核调用信号处理器完成,所以进程使用raise时,信号立即传递并被处理,甚至在raise调用返回前。raise函数调用成功返回0,失败(唯一的失败是EINVALsig无效)返回非0

  1. /**
  2. *
  3. * 所以这个函数一定是先输出handle done
  4. * 再输出result
  5. *
  6. */
  7. #include <unistd.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <signal.h>
  11. void handler(int sig)
  12. {
  13. printf("handle done\n");
  14. }
  15. int main(int argc, char **argv)
  16. {
  17. signal(SIGINT, handler);
  18. int result = raise(SIGINT);
  19. printf("%d\n", result);
  20. return 0;
  21. }

sigqueue()

向进程发送信号,没用过也没实验过

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

进程组通知:killpg()

  1. #include <signal.h>
  2. int killpg(pid_t pgrp, int sig)

向某一进程组的所有成员发送信号,相当于kill(-pgrp, sig);

等待信号:pause()

暂停进程执行,知道信号处理器被调用,中断pause。被中断程序返回-1,并设置errno。

  1. #include <unistd.h>
  2. int pause();

显示信号信息:strsignal()

有三种显示的方式

  1. sys_siglist数组,使用sys_siglist[SIGXXX]获得信号的描述
  2. strsignal()函数,返回信号描述的字符串,推荐使用strsignal()函数,因为会有安全的边界检查,而且该函数设置了地区敏感,可以显示本地语言(没感觉出来)
  3. psignal()函数,在标准错误设备上输出msg信息和sig的描述
  1. #define _BSD_SOURCE
  2. // 5.4.0-70-generic下使用这个红出现了警告
  3. // # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
  4. #include <signal.h>
  5. extern const char *const sys_siglist[];
  6. #define _GNU_SOURCE
  7. #include <string.h>
  8. char *strsignal(int sig)
  9. #include <signal.h>
  10. void psignal(int sig, const char *msg);

信号集

用于表述多个信号的数据结构,sigset_t,这在Linux上以为掩码形式存在。

初始化信号集

sigemptyset()以空的形式初始化信号集,sigfillset()以填充所有信号的形式初始化。SUSv3只要求对sigset_t赋值即可,sigset_t其实可以用手动赋值,但这样有损于可移植性,而Linux使用函数实现,增强了可移植性。

  1. #include <signal.h>
  2. int sigemptyset(sigset_t *__set)
  3. int sigfillset(sigset_t *__set)

操作信号集

分别有从信号集中添加和删去某个信号,或是判断这个信号是否在信号集中

  1. #include <signal.h>
  2. int sigaddset(sigset_t *set, int sig)
  3. int sigdelset(sigset_t *set, int sig)
  4. int sigismember(sigset_t *set, int sig);
  5. int sigpending(sigset_t *set);
  • sigismembersig存在于set中返回1,否则返回0
  • sigpending:返回调用进程处于等待的信号

GNU C的拓展

需要在宏中添加#define _GNU_SOURCE

具体的三个函数这里没有写

信号掩码

信号传递的阻塞:

每个进程拥有一个信号掩码,由内核维护,记录着需要阻塞的信号。如果内核发送该进程的信号掩码中记录的信号给该进程,那么这个信号会被阻塞,除非从进程掩码中移除。更进一步,信号掩码可以细致到线程级别

sigprocmask函数

  1. 修改该进程的信号掩码
  2. 获得该进程的信号掩码
    • 设置setNULLhowSIG_BLOCK,可以用oldset中获得信号掩码
  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset)
  • how指定了函数的具体行为
    • SIG_BLOCK,将set信号集中的信号添加到信号掩码中
    • SIG_UNBLOCK,将set中的信号从掩码中移除
    • SIG_SETMASK,将set信号集赋值给掩码
  • set指定的程序需要处理的信号的信号集
  • oldset得到修改之前的信号掩码

信号处理器

概念

信号处理器和主程序是两条独立的线程,同属于同一进程。

可重入函数

同一进程多个线程看同时安全(产生预期的结果)调用的函数

要求

  • 只是用本地变量

不可重入函数的特点

  • 使用全局变量和静态数据结构可能是不可重入
  • 使用静态分配的内存,这次调用会覆盖上次调用的信息

常见不可重入函数举例

  • 不可重入
    • malloc函数族

异步信号安全函数

可重入或信号处理器函数无法中断的函数

计时器与休眠

定时器精度问题:没有写,见UNIX编程手册23.2章和10.6章

计时器

linux对每个进程设置3个计时器计时器的种类有:

真实计时器 虚拟计时器 实用计时器
C语言中的值 ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF
记录时间 程序运行的总时间 在用户态的时间之和 在用户态和内核态的时间之和
到期发送信号 SIGALRM SIGVTALRM SIGPROF

对这些信号的默认处理是终止进程,除非自定义信号处理函数。

间隔计时器

计时器数据结构:

  1. struct itimerval
  2. {
  3. struct timeval it_interval;
  4. struct timeval it_value;
  5. };
  6. struct timeval // 时间的数据结构
  7. {
  8. long tv_sec; // Seconds
  9. long tv_usec; // Microseconds
  10. };

系统使用settimer创建定时器

  1. #include <sys/times.h>
  2. int setitimer(int which, const struct itimerval *new, struct itimerval *old)
  • which指明需要创建哪种计时器
    • 使用C语言预定义值指明
  • new
    • it_value指明定时器到期的计时时间
      • 两个值都为0表示屏蔽计时器
      • 值表示初始的间隔时间,即第一次发送信号的时间间隔
    • it_interval指明定时器是否是周期性定时器
      • 0时不表示间隔时间是0,而是表示计时器不是周期性的,是一次性的
      • 不为0时表示计时value后每次间隔interval再发送信号
  • old
    • old不为NULL时,则该值指向函数设定计时器时的前一个设置,用于计时器设定的还原

函数行为:计时器会从new.it_value开始倒计时直到0为止,递减至0时发送信号,若new.it_interval != 0,重置并开始计时

一个进程只能拥有三种计时器的一种,所以之后再次调用setitimer时会修改上一次的设定值

  1. #include <sys/times.h>
  2. int getitimer(int which, struct itimerval *value)

获得当前计时器的状态,类似于setitimer中的old

  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds)

设定一个sedonds秒后到期的计时器,到期时发送SIGALRM信号,(这也会覆盖之前的设定,alarm(0)表示屏蔽所有计时器)

休眠

  1. #include <unistd.h>
  2. unsigned int sleep(unsigned int seconds); // 休眠seconds秒
  3. void usleep(unsigned long usec); // 休眠usec * 10 ^ -6秒
  4. // 这两个函数已经进行一次抽象了
  5. // 等价于调用
  6. unsigned int alarm(unsigned int seconds);
  7. int pause(void);

sleep函数正常休眠,返回0,如果因为信号中断休眠,返回剩余休眠的时间。

高精度

  1. #include <time.h>
  2. int nanosleep(const struct timespec *requested_time, struct timespec *remaining)
  3. struct timespec
  4. {
  5. long tv_sec; /* Seconds. */
  6. long tv_nsec; /* Nanoseconds. */
  7. };

该函数的实现不依赖与信号,so???

  • requested_time
    • 指明休眠时间,支持纳秒级别

原文链接:http://www.cnblogs.com/dwr2001/p/14830990.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号