当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 学习笔记 > 嵌入式学习笔记:linux进程通信之信号函数

嵌入式学习笔记:linux进程通信之信号函数 时间:2018-09-26      来源:未知

1、信号注册函数: signal

#include

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

sighandler_t signal(int signum, sighandler_t handler);

入参:

signum 哪个信号

handle 信号所对应的处理函数;SIG_IGN:忽略此信号;SIG_DFL:按系统默认

方式处理

该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

2、修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)

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

成功:0;失败:-1,设置errno

参数:

act:传入参数,新的处理方式。

oldact:传出参数,旧的处理方式。

struct sigaction结构体

struct sigaction {

void

void

sigset_t

int

void

(*sa_handler)(int);

(*sa_sigaction)(int, siginfo_t *, void *);

sa_mask;

sa_flags;

(*sa_restorer)(void);

};

sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)

sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)

重点掌握:

① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作

② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。

③ sa_flags:通常设置为0,表使用默认属性。

内核实现信号捕捉过程:

信号捕捉特性:

1、进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。

2、XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。

3、阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排

队)

3、pause函数 : 挂起当前的进程,直到收到一个信号,才会接着执行

调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃cpu) 直到有信号递达将其唤醒。

int pause(void);

返回值:-1 并设置errno为EINTR

① 如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。 ② 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回。 ③ 如果信号的处理动作是捕捉,则【调用完信号处理函数之后,pause返回-1】errno设置为 EINTR,表示“被信号中断”。想想我们还有哪个函数只有出错返回

值。

④ pause收到的信号不能被屏蔽,如果被屏蔽,那么pause就不能被唤醒。

4、alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。

每个进程都有且只有唯一个定时器。

unsigned int alarm(unsigned int seconds);

返回0或剩余的秒数,无失败。

常用:取消定时器alarm(0),返回旧闹钟余下秒数。

例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0)

定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。

使用time命令查看程序执行的时间。程序运行的瓶颈在于IO,优化程序,首选优化IO。

实际执行时间 = 系统时间 + 用户时间 + 等待时间

5、raise函数: 自己给自己发送信号 raise(signo) == kill(getpid(), signo);

int raise(int sig);

成功:0,失败非0值

入参: 发送的信号

6、abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件 void abort(void); //该函数无返回

kill函数/命令产生信号

1、kill命令产生信号:kill -SIGKILL pid

2、kill函数:给指定进程发送指定信号(不一定杀死)

7、int kill(pid_t pid, int sig);

成功:0;失败:-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。

pid > 0: 发送信号给指定的进程。

pid = = 0: 发送信号给 与调用kill函数进程属于同一进程组的所有进程。

pid < 0: 取|pid|发给对应进程组。

pid = = -1:发送给进程有权限发送的系统中所有进程。

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID

相同。

权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。 kill -9 (root用户的pid) 是不可以的。同样,普通用户也不能向其他

普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

 

时序竞态

设想如下场景:

欲睡觉,定闹钟10分钟,希望10分钟后闹铃将自己唤醒。

正常:定时,睡觉,10分钟后被闹钟唤醒。

异常:闹钟定好后,被唤走,外出劳动,20分钟后劳动结束。回来继续睡觉计划,但劳动期间闹钟已经响过,不会再将我唤醒。

时序问题分析

回顾,借助pause和alarm实现的mysleep函数。设想如下时序:

1. 注册SIGALRM信号处理函数 (sigaction...)

2. 调用alarm(1) 函数设定闹钟1秒。

3. 函数调用刚结束,开始倒计时1秒。当前进程失去cpu,内核调度优先级高的进程(有多个)取代当前进程。当前进程无法获得cpu,进入就绪态等待cpu。

4. 1秒后,闹钟超时,内核向当前进程发送SIGALRM信号(自然定时法,与进程状态无

关),高优先级进程尚未执行完,当前进程仍处于就绪态,信号无法处理(未决)

5. 优先级高的进程执行完,当前进程获得cpu资源,内核调度回当前进程执行。

SIGALRM信号递达,信号设置捕捉,执行处理函数sig_alarm。

6. 信号处理函数执行结束,返回当前进程主控流程,pause()被调用挂起等待。(欲

等待alarm函数发送的SIGALRM信号将自己唤醒)

7. SIGALRM信号已经处理完毕,pause不会等到。

解决时序问题

可以通过设置屏蔽SIGALRM的方法来控制程序执行逻辑,但无论如何设置,程序都有可能在“解除信号屏蔽”与“挂起等待信号”这个两个操作间隙失去cpu资源。除非将这两步骤合并成一个“原子操作”。sigsuspend函数具备这个功能。在对时序要求严格的场

合下都应该使用sigsuspend替换pause。

int sigsuspend(const sigset_t *mask);//挂起等待信号。

sigsuspend函数调用期间,进程信号屏蔽字由其参数mask指定。

可将某个信号(如SIGALRM)从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然屏蔽该信号。

竞态条件,跟系统负载有很紧密的关系,体现出信号的不可靠性。系统负载越严重,信号不可靠性越强。

不可靠由其实现原理所致。信号是通过软件方式实现(跟内核调度高度依赖,延时性强),每次系统调用结束后,或中断处理处理结束后,需通过扫描PCB中的未决信号集,来判断是否应处理某个信号。当系统负载过重时,会出现时序混乱。

这种意外情况只能在编写程序过程中,提早预见,主动规避,而无法通过gdb程序调试等其他手段弥补。且由于该错误不具规律性,后期捕捉和重现十分困难。

8、setitimer函数

设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

成功:0;失败:-1,设置errno

参数:which:指定定时方式

① 自然定时:ITIMER_REAL → 14)SIGLARM 计算自然时间

② 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占

用cpu的时间

③ 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF 计算占用cpu及执行系

统调用的时间

信号集操作函数:

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。

信号集设定:

sigset_t set; // typedef unsigned long sigset_t;

int sigemptyset(sigset_t *set); //将某个信号集清0

成功:0;失败:-1

int sigfillset(sigset_t *set); //将某个信号集置

1

成功:0;失败:-1

int sigaddset(sigset_t *set, int signum); //将某个信号加入信号集

成功:0;失败:-1

int sigdelset(sigset_t *set, int signum); //将某个信号清出信号集

成功:0;失败:-1

int sigismember(const sigset_t *set, int signum);//判断某个信号是否在信号

集中

返回值:在集合:1;不在:0;出错:-1

sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证

跨系统操作有效。

对比认知select 函数。

9、sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB

中)

严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号

丢处理。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 成功:0;失败:-1,设置errno

参数:

set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。

oldset:传出参数,保存旧的信号屏蔽集。

how参数取值:假设当前的信号屏蔽字为mask

SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set

SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set

SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于 mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回

前,至少将其中一个信号递达。

10、sigpending函数

读取当前进程的未决信号集

int sigpending(sigset_t *set); //set传出参数。

返回值:成功:0;失败:-1,设置errno

上一篇:嵌入式学习笔记:linux进程间通信-消息队列、信号集

下一篇:嵌入式学习笔记:指针函数详解

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部