当前位置:   article > 正文

Unix环境高级编程-信号详解_mask & pending

mask & pending

信号

    信号是初步异步,线程是强烈异步
    两者机制很少混合使用,仅仅会将少量的信号混入
  • 1
  • 2

同步:

顺序执行,可预知
  • 1

异步:

哪个事件什么时候到来不确定,产生什么样的结果也不确定

异步事件的处理:
查询法:适合事件发生频率比较高
通知法:适合事件发生频率比较稀疏
  • 1
  • 2
  • 3
  • 4
  • 5

一、信号

1、信号的概念-

信号是软件层面的中断
信号的响应依赖与中断机制
多半的信号的作用都是终止,或者终止+core
标准C里面的信号基本是摆设
core文件就是出错现场,利于找出程序错误
  • 1
  • 2
  • 3
  • 4
  • 5

2、signal();

NAME
   signal - ANSI C signal handling

SYNOPSIS
   #include <signal.h>

   typedef void (*sighandler_t)(int);
   sighandler_t signal(int signum, sighandler_t handler);


原型:void (*signal(int signum, void (*func)(int)))(int);

信号会打断阻塞的系统调用。
这就是说,在以前的程序中,有一行代码,用来忽视所有信号
只保留有用的信号的原因,就是为了防止自己的程序被信号打断。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3、信号的不可靠

标准信号一定会丢失,实时信号是不丢失的。
信号不可靠是指信号的行为不可靠。
很有可能第一次信号调用还没有结束的时候就发生了第二次调用
  • 1
  • 2
  • 3

4、可重入函数

解决信号不可靠的问题。
一种特殊的函数:第一次调用还没有结束的情况下,第二次调用也不会出错。
所有的系统调用都是可重入的,一部分库函数也是可重入的,比如说memcpy,memmove
特别是关于指针的函数,要特别注意

拿rand函数(随机函数)举例,rand生成的数是伪随机数,当前的随机数是在上一个随机数的基础上生成的,
如果第一个随机数还没有生成,rand就被调用第二次,就会出现结果错误。

在函数名字中(linux中),有_r版本的,这个版本的就是可重入函数,就是可以用在信号处理函数里面的。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5、信号的响应过程

信号从收到到响应,有一个不可避免的延迟。
思考:如何忽略掉一个信号?
      标准信号为什么要丢失?

一个信号的响应过程:
不是说程序随时有信号,随时可以检测到,而是只有在从内核态过渡到用户态的时候,
执行mask&pending之后才知道自己有没有信号,所以,程序接收到信号之后,仍然继续执行,
等程序进入内核态(比如说时间片到,程序和程序的信息被挂在内存),然后再到该程序执行的时候,
程序会将自己的mask和pending进行与运算,如果有哪一位变化,就知道有信号,就会把下一个要执行的地址
替换为信号响应函数的地址,去执行信号响应函数,之后再回到内核态,再设置mask和pending位,
再进入用户态,再进行与运算,发现信号已经处理完毕,正常执行后面的语句。

标准信号的响应,没有严格的顺序。
一般是先响应情节比较严重的信号,比如说段错误优先程序终止

而这个mask代表某一个信号怎么执行,或者怎么响应,pending代表信号是否到来,都是位图。
不能阻止信号到来,只能决定信号是否响应。

位图结构不管来多少个信号,都只有一个1,因为位图不累计。

那么对于标准信号,同时来一万个信号,也是只有一个1,所以只会响应一次。
但是对于实时信号,不会有信号丢失。

不能从信号处理函数中随意的往外跳,setjmp(),longjmp()
因为它们会错过一个把mask操作改回去的重要操作,导致以后永远错过某一个信号
所以setjmp(),longjmp()绝对不能在信号函数里面使用。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

6、常用函数

1.kill(); // 用来发信号的

虽然在linux里面,用kill杀死进程,但它不是杀死,它是发信号,是因为大多数信号都是终止。
NAME
   kill - send signal to a process

SYNOPSIS
   #include <sys/types.h>
   #include <signal.h>

   int kill(pid_t pid, int sig);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.raise(); // 给当前进程发送一个信号,自己给自己发

NAME
   raise - send a signal to the caller

SYNOPSIS
   #include <signal.h>

   int raise(int sig);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.alarm(); //有了alarm,就有了精确的时间概念

用信号计时,10ms以内的不准确(理论上)
NAME
   alarm - set an alarm clock for delivery of a signal

SYNOPSIS
   #include <unistd.h>

   unsigned int alarm(unsigned int seconds);
alarm没办法实现多任务的计时,只能有一个alarm

当有多个alarm定时的时候,只有最后一个alarm生效
例子:使用单一计时器,构造一组函数,实现任意数量的计时器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.1setitimer(); //时间控制更好没有时间延迟

setitimer更精确,不想alarm只能到秒,而且它有多种时钟模式
任意数量的计时器封装成库:anytime
NAME
   getitimer, setitimer - get or set value of an interval timer

SYNOPSIS
   #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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.pause();

NAME
   pause - wait for signal

SYNOPSIS
   #include <unistd.h>
   int pause(void);

有一些环境中,sleep是使用alarm和pause来封装的,因为alarm存在bug,所以sleep一定存在bug
所以在真正发布的程序中,不可能有sleep存在。
但是有的环境中,sleep是使用nanosleep封装的,所以在当前环境没事。
所以在需要移植的程序中,不要用sleep

流控算法:信号令牌桶封装成库-mytbf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5.abort();

NAME
   abort - cause abnormal process termination

SYNOPSIS
   #include <stdlib.h>

   void abort(void);
人为的制造一个异常
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

6.system();

NAME
   system - execute a shell command

SYNOPSIS
   #include <stdlib.h>

   int system(const char *command)
调用shell,来完成一条shell命令
可以简单的看成是few的封装
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

7.sleep();

因为有的环境sleep是使用alarm+pause封装的,
而alarm多余一个的时候是会出错的,所以我们不用sleep
7.1 nanosleep();
7.2 usleep();
7.3 select();这个函数的副作用可以用来休眠,而且是安全可靠的
  • 1
  • 2
  • 3
  • 4
  • 5

7、信号集

NAME
   sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set oper‐
   ations

信号集合类型:sigset_t

SYNOPSIS
   #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);// 删除某一个信号

   int sigismember(const sigset_t *set, int signum);// 判断信号是否存在信号集合当中
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

8、信号屏蔽字/pending集的处理

mask屏蔽字:位图全为1
pending:位图全为0
当一个进程从内核态返回用户态的时候,会进行mask&pending

mask集合的操作:
sigprocmask(); // 控制mask,可以决定信号是否被响应

NAME
   sigprocmask, rt_sigprocmask - examine and change blocked signals

SYNOPSIS
   #include <signal.h>

   /* Prototype for the glibc wrapper function */
   // 把set信号集的所有信号全部设置为how
   int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

   /* Prototype for the underlying system call */
   int rt_sigprocmask(int how, const kernel_sigset_t *set,
                      kernel_sigset_t *oldset, size_t sigsetsize);

   /* Prototype for the legacy system call (deprecated) */
   int sigprocmask(int how, const old_kernel_sigset_t *set,
                   old_kernel_sigset_t *oldset);


pending集的操作:
这是一个系统调用,所以一定会进入内核
它的作用是进入内核,去取出pending的状态
但是取出来的信号是已经响应完的信号,因为pending在出内核态的时候,
进行完与mask的与操作,就自动去执行存在的信号,然后重置mask和pending,
所以,这个时候你带回来的pending位图是旧的位图,是信号响应之前的位图
所以,这个函数的价值不高,没有什么用,用maks操作函数就够了
sigpending();
NAME
   sigpending, rt_sigpending - examine pending signals

SYNOPSIS
   #include <signal.h>

   int sigpending(sigset_t *set);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

9、扩展

sigsuspend(); // 可以做信号驱动程序
NAME
   sigsuspend, rt_sigsuspend - wait for a signal

SYNOPSIS
   #include <signal.h>

   int sigsuspend(const sigset_t *mask);

功能和pause很类似都是等待一个信号
但是pause和其他信号函数共同做成的信号驱动程序不是原子的。
所以pause不能用来作信号驱动程序,用锁可以



sigaction(); 代替 signal() 这个函数
NAME
   sigaction, rt_sigaction - examine and change a signal action

SYNOPSIS
   #include <signal.h>

   int sigaction(int signum, const struct sigaction *act,
                 struct sigaction *oldact)


有了setitimer和sigaction之后,程序中就不要再用alarm和signal
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

10、实时信号(上面是标准信号)

实时信号不会丢失,ulimit可以查看信号队列的最大个数,可以修改

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/864799
推荐阅读
相关标签
  

闽ICP备14008679号