

使用signal()函数处理时,只需要指出要处理的信号和处理函数即可。它主要是用于前32种非实时信号的处理,不支持信号传递信息,Linux还支持一种更健壮、更新的信号处理函数sigaction(),该函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)。是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该使用这个接口)给信号signum设置新的信号处理函数act, 同时保留该信号原有的信号处理函数oldact
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
其中sigaction结构体:
struct sigaction{
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
};
sa_handler字段包含一个信号捕捉函数的地址
sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。
sa_flag是一个选项,主要理解两个
SA_INTERRUPT 由此信号中断的系统调用不会自动重启
SA_RESTART 由此信号中断的系统调用会自动重启
SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
最后一个参数是一个替代的信号处理程序,当设置SA_SIGINFO时才会用。
写一个程序和上个例子相同效果:
#include <stdio.h> #include <signal.h> #include <stdlib.h> void myfun(int sign_no) { if(sign_no == SIGINT) { printf("get sigint \n"); } else if(sign_no == SIGQUIT) { printf("get sigquit \n"); } } /* struct sigaction{ void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; void (*sa_restore)(void); }; int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact); */ int main() { struct sigaction action; /*初始化结构体*/ action.sa_handler = myfun; sigemptyset(&action.sa_mask); action.sa_flags = 0; sigaction(SIGQUIT,&action,0); sigaction(SIGINT,&action,0); pause(); exit(0); }

2、信号集函数组
我们需要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。信号集函数组包含几大模块: 创建函数集、登记信号集、检测信号集。
创建函数集
#include <signal.h>
int sigemptyset(sigset_t * set) ;
int sigfillset(sigset_t * set) ;
int sigaddset(sigset_t * set,int signo) ;
int sigdelset(sigset_t * set,int signo) ;
四个函数返回:若成功则为0,若出错则为-1
int sigismember(const sigset_t * set, int signo) ;
返回:若真则为1,若假则为0。
sigemptyset: 初始化信号集合为空。
sigfillset: 初始化信号集合为所有信号的集合。
sigaddset: 将指定信号添加到现存集中。
sigdelset: 从信号集中删除指定信号。
sigismember: 查询指定信号是否在信号集合中。
登记信号集
登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数判断检测或更改信号屏蔽字,然后使用sigaction函数改变进程接受到特定信号之后的行为。
一个进程的信号屏蔽字可以规定当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功则为0,若出错则为-1
oset是非空指针,进程是当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
用sigprocmask更改当前信号屏蔽字的方法。how参数设定:
SIG_BLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
SIG_NUBLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号。
SIG_SETMASK该进程新的信号屏蔽是set指向的值。如果set是个空指针,则不改变该进程的信号屏蔽字,how的值也无意义
一般处理流程为:
创建信号集---》设置屏蔽位---》定义信号处理函数
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <signal.h> #include <unistd.h> void myfun() { printf("hi ,SIGQUIT;you can try SIGINT!\n"); } int main() { struct sigaction action,action1; sigset_t set; /*创建信号集合*/ if(sigemptyset(&set) < 0) //初始化信号集合 { perror("sigemptyset error!\n"); exit(1); } if(sigaddset(&set,SIGQUIT) < 0)//添加信号到集合 { perror("sigaddset error!\n"); exit(1); } if(sigaddset(&set,SIGINT) < 0) //添加信号 { perror("sigaddset error\n"); exit(1); } /*信号处理*/ if(sigismember(&set,SIGQUIT)) { action.sa_handler = myfun; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(SIGQUIT,&action,NULL); } if(sigismember(&set,SIGINT)) { action1.sa_handler = SIG_DFL; action1.sa_flags = 0; sigemptyset(&action1.sa_mask); sigaction(SIGINT,&action1,NULL); } /*设置屏蔽位*/ if(sigprocmask(SIG_BLOCK,&set,NULL) < 0) { perror("sigprocmask error\n"); exit(1); } else { printf("signal set was block,press any key\n"); getchar(); } if(sigprocmask(SIG_UNBLOCK,&set,NULL) < 0) { perror("sigprocmask error\n"); exit(1); } else { printf("signal set is in unblock state\n"); } while(1); exit(0); }


十四、信号量通信
在以前的ucos_ii中也接触过信号量的概念,用来解决同步和互斥问题的。在linux中信号量就是用来解决进程间的同步与互斥问题的一种进程间通信机制。
1、同步和互斥
在创建子进程时,你是怎么保证父子进程执行的先后顺序呢?我在以前的时候是通过sleep()函数来实现的,比如我想让子进程先运行再让父进程运行,那么我就在父进程的程序中加一个sleep()函数,让父进程先睡眠,这样子就能先执行子进程了。有的时候咱们事先无法知道父进程和子进程哪一个先执行,但是要向我那样使用sleep()函数,只能保证先执行子进程,但是不能保证子进程执行完后再执行父进程,这样说能理解吧。所以如果我们想要子进程完全执行完后再执行父进程,就可以利用信号量来解决它们之间的同步问题。再举个更通俗的例子,一条食品生产线上,假设A、B共同完成一个食品的包装任务,A负责将食品放到盒子里,B和C负责将盒子打包。必须得是A先装食品B再打包吧,要是B不按规则先打包,那A还装啥,所以就需要一种机制方法保证A先进行B再进行,“信号量”就是这种机制方法,AB之间的关系就是同步关系;再假设打包要用到刀子,而车间就有一把刀子,这时候B和C就构成了互斥关系。
2、信号量
在多任务操作系统环境下,多个进程会同时运行,并且一些进程间可能会存在一定的关联。多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而且在不同进程间,为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程间的互斥关系。
进程间的互斥关系与同步关系存在的根源在于临界资源。临界资源是在同一时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其它外围设备等)和软件资源(共享代码段、共享结构和变量等)。访问临界资源的代码叫做临界区,临界区本身也会称为临界资源。
信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(P/V操作)。PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期 望的消息尚未产生;当信号 量的值非0时,表示期望的消息已经存在。用P V操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。
3、二维信号量
最简单的信号量只能取0和1值,这种信号量叫做二维信号量,二维信号量初始化时设置初始值为1。
在Linux系统中,使用信号量通常分为以下4个步骤:
① 创建信号量或获得在系统中已存在的信号量,此时需要调用 semget() 函数。不同进程通过使用同一个信号量键值来获得同一个信号量。
② 初始化信号量,此时使用 semctl() 函数的SETVAL操作。当使用二维信号量时,通常将信号量初始化为1。
③ 进行信号量的PV操作,此时,调用 semop()函数。这一步是实现进程间的同步和互斥的核心工作部分。
④ 如果不需要信号量,则从系统中删除它,此时使用semctl()函数的 IPC_RMID操作。需要注意的是,在程序中不应该出现对已经被删除的信号量的操作。
在之前创建子进程的例子中都是通过sleep函数来让子进程先运行,但是无法保证的是,子进程运行结束后马上运行父进程。现在这个例子就是通过信号量实现同步。
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdlib.h> #include <unistd.h> union semun { int val; struct semid_ds *buf; unsigned short *array; }; /*函数声明*/ int init_sem(int sem_id,int val); //初始化 int sem_p(int sem_id); //P操作 int sem_v(int sem_id); //V操作 int del_sem(int sem_id); //删除 void main() { pid_t res; int sem_id; sem_id = semget(ftok(".",1),1,0666 | IPC_CREAT); //获取或者是创建信号量 init_sem(sem_id,0); //初始化 if((res = fork()) < 0) //创建子进程 { perror("fork\n"); exit(1); } if(res == 0) //子进程 { printf("child progress wait for 3 seconds\n"); sleep(3); printf("child progress %d\n",getpid()); sem_v(sem_id); } else { sem_p(sem_id); printf("parent progress %d\n",getpid()); sem_v(sem_id); del_sem(sem_id); } exit(0); } int init_sem(int sem_id,int val) { union semun sem_union; sem_union.val = val; if(semctl(sem_id,0,SETVAL,sem_union) < 0) { perror("init sem \n"); return 1; } return 0; } int sem_p(int sem_id) { struct sembuf op; op.sem_num = 0; op.sem_op = -1; op.sem_flg = SEM_UNDO; if(semop(sem_id,&op,1) < 0) { perror("sem_p\n"); return 1; } return 0; } int sem_v(int sem_id) { struct sembuf op; op.sem_num = 0; op.sem_op = 1; op.sem_flg = SEM_UNDO; if(semop(sem_id,&op,1) < 0) { perror("sem_p\n"); return 1; } return 0; } int del_sem(int sem_id) { union semun sem_union; if(semctl(sem_id,0,IPC_RMID,sem_union) < 0) { perror("del_sem\n"); return 1; } return 0; }
运行的效果如下:
在程序中还有一个ftok()函数。
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
可以查看百度百科的介绍:http://baike.baidu.com/view/2287465.htm?fr=aladdin
回复
打赏帖 | |
---|---|
【S32K146】S32DS watchdog 配置使用被打赏20分 | |
【Zephyr】使用 IAR 调试 Zephyr 镜像被打赏20分 | |
【Zephyr】MCXN947 Zephyr 开发入门适配shell被打赏20分 | |
【我要开发板】6.联合MATLAB记录数据被打赏50分 | |
【瑞萨RA2E1开发板】:使用ADC功能实现位移传感器采集方案被打赏20分 | |
【nRF7002DK】基于sht30的温湿度计被打赏20分 | |
【nRF7002DK】日志打印被打赏20分 | |
【换取手持示波器】RGB屏幕移植ARM-2D库被打赏35分 | |
【分享开发笔记,赚取电动螺丝刀】分享一下如何解决瑞萨RA2E1使用printf编译报错问题被打赏27分 | |
rtthread硬件加密-5hash加密分析被打赏10分 |