一、信号量semaphore
1.1概念
也称信号灯,信号量本质上是一个计数器(非负整数),主要用来保护临界资源(共享资源),这种资源同一时刻仅供一个进程(线程)使用,它的值表示系统中该资源的数量(若为0当然不允许使用),因此可以实现进程(线程)间的同步与互斥。对于共享内存,所有进程都可以访问,就容易导致读写冲突,此时就很需要信号量来配合使用。
同步指在共享它的进程或线程间保持数据一致
互斥指同一时刻仅供一个进程(线程)使用资源
信号量只能通过三种操作来访问:
-
初始化
-
P操作(申请使用资源,属于"消费者")
-
如果信号量>0,信号量-1返回;
-
如果信号量==0,则睡眠等待直到信号量>0然后立即-1返回
-
-
V操作(释放资源,属于"生产者")
-
无进程/线程等待时+1,
-
有进程/线程等待资源时(说明信号量为0)则唤醒并让其返回
-
linux用户态进程提供三种信号量,POSIX有名信号量、POSIX无名信号量、SYSTEM V信号量。
有名信号量的值保存在文件中(具体是保存在/dev/shm/目录下,使用时需要链接库),它既可以用于进程(包括不相关进程)也可以用于线程;
无名信号量的值保存在内存中,Linux只支持线程同步。
除了创建和删除不同,PV操作对应的API都相同
1.2创建
#include <semaphore.h>
#include <pthread.h>//创建有名信号量
sem_t *sem_open(char *name, int oflag, mode_t mode, unsigned int value)@param: name 信号量文件名字,不要带路径,因为有默认存放的目录oflag 常用O_CREATmode 常用0666value 信号量初始值(二值信号量为1,普通信号量表示资源数目)@return: 成功返回信号量指针,失败返回SEM_FAILED with errno值
#include <semaphore.h>
#include <pthread.h>//创建无名信号量
int sem_init(sem_t *sem,int pshared,unsigned int value);@param: sem 信号量指针 pshared 0value 信号量初始值(二值信号量为1,普通信号量表示资源数目)@return: 成功返回0,失败返回-1
1.3PV操作
#include <semaphore.h>
#include <pthread.h>sem_wait(sem_t *sem)
申请使用资源,如果信号量>0,信号量-1返回;如果信号量==0,则睡眠等待直到信号量>0然后立即-1返回sem_post(sem_t *sem)
释放资源,无进程/线程等待时+1,有进程/线程等待资源时(说明信号量为0)则唤醒并让其返回
1.4关闭(有名信号量专用)
#include <semaphore.h>
#include <pthread.h>//关闭有名信号量的引用
sem_close(sem_t *sem)
一旦打开使用了有名信号量,关闭它的引用就很重要,因为后续的删除需要等系统中所有进程或线程关闭了引用才能生效,每个有名信号量都有一个引用计数器记录它的打开次数,sem_unlink必须等待最后一个sem_close完成!
1.5删除
#include <semaphore.h>
#include <pthread.h>//删除有名信号量
sem_unlink(char *name)
#include <semaphore.h>
#include <pthread.h>//删除无名信号量
int sem_destroy(sem_t *sem)
二、信号signal
2.1概念
信号是软中断(软件层次上对中断机制的一种模拟),linux操作系统中用于进程间通信、处理异常情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知,用于通知该进程或线程某种事件已经发生,需要做出相应的处理。
每个进程收到的所有信号,都是由内核负责发送的。每个信号都有编号、名称和默认处理动作,可通过命令kill -l显示查看所有信号
1-31为常规信号(也称标准信号或普通信号),34-64为实时信号,驱动编程与硬件相关
2.2常用信号
常用信号:
信号名 | 含义 | 默认动作 |
---|---|---|
SIGHUP | 终端关闭时产生,发给和该终端关联的会话内所有进程 | 终止 |
SIGINT | 终端输入ctrl C时产生,发给当前终端的所有前台进程 | 终止 |
SIGQUIT | 终端输入ctrl \ 时产生,和SIGINT类似 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法指令时产生 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针、缓冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代表“管道断裂” | 终止 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停进程,并且不能被捕捉和忽略 | 暂停进程 |
SIGALRM | 该信号用于通知某进程定时器时间已到 | 终止 |
SIGCHLD | 子进程状态改变时发给父进程 | 忽略 |
2.3信号的处理方式
-
忽略(SIGKILL、SIGSTOP除外)
-
默认动作
-
捕捉(将默认动作改为用户调用的处理函数,同样地,SIGKILL、SIGSTOP除外)
2.4信号的发送
1)shell命令 :kill -[信号编号] [pid]
2)程序发送: kill/raise/sigqueue函数
int kill(pid_t pid, int signum)
@param: pid >0 发送给指定进程=0 发送给同一进程组的所有进程<-1 发送给|pid|同一进程组的所有进程=-1 发送给有权限发送的系统中所有进程@return:成功返回0失败返回-1int raise(int signum) 给自己发信号 等价于 kill(getpid(),signum)sigqueue(pid_t pid, int signum, const union sigval value)
功能:比一般的信号发送还多传递了信号携带的数据,接收信号的进程可以根据数据做出不同响应
通常搭配sigaction使用@param: value 联合体sigval变量union sigval {int sival_int; //这个常用一些void *sival_ptr;
};
示例:
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char **argv)
{ if(argc < 3){perror("missing params\n");return 0;}pid_t pid;int signum;signum = atoi(argv[1]);pid = atoi(argv[2]);union sigval value;value.sival_int = 520;sigqueue(pid,signum,value);return 0;
}
2.5信号的捕捉
signal(int signum, sighandler_t handler)
功能:捕捉指定的信号,修改其默认动作 为 自定义的处理函数@param: signum 信号编号,也可使用对应的信号名
handler 自定义函数名,SIG_DFL 为默认动作,SIG_IGN 为忽略
(函数名是函数的入口地址,这里的sighandler_t 是函数指针类型名 通过typedef void(*sighandler_t)(int)实现,因此自定义函数也要设置成无返回值,参数为1个int型的 )
//由于signal在不同类unix系统的行为不完全一样,系统建议使用sigaction函数
sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
@param: 信号名、新动作、旧动作(一般都给NULL,表示不关心)
struct sigaction{
void (*sa_handler)(int); //处理函数指针
void (*sa_sigaction)(int signum,siginfo_t *,void *); //与sigqueue搭配使用,获取它发送的信号携带的数据
sigset sa_mask; //设置阻塞参数
int sa_flags; //使用sa_sigaction成员时需要设置为SA_SIGINFO 不使用时给0
void (*sa_restore)(void);//废弃的数据域
};
/******************************自定义函数的参数*****************************/
//siginfo_t xxx 成员如下
siginfo_t{
pid_t si_pid; //哪个进程发送的
int si_int; //数据内容
sigval_t si_value; //sigval联合体中的数据 和si_int没区别
};
union sigval {
int sival_int; //这个常用一些
void *sival_ptr;
};
void * xxx //xxx为 NULL 无数据, 非NULL 有数据
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>void print_status(int status);void handler(int signum)
{int status;if(signum == SIGCHLD){wait(&status);print_status(status);}
}int main()
{struct sigaction act; act.sa_handler = handler; //normal signalact.sa_flags = 0;sigemptyset(&act.sa_mask); printf("before fork\n");pid_t pid;pid = fork();if(pid < 0){perror("fork");return 0;}else if(pid > 0) //father process{sigaction(SIGCHLD,&act,NULL);while(1){puts("this is father,i'm sleeping");sleep(1);} }else if(pid == 0) //child process{sleep(5);exit(7);}return 0;
}void print_status(int status)
{if(WIFEXITED(status)) //exit normal{printf("child process finished and exit normal,exit status:%d\n",WEXITSTATUS(status));}else if(WIFSIGNALED(status)) //exit abnormal{printf("child process finished but exit abnormal,exit status:%d\n",WTERMSIG(status));}}
2.6定时器函数
unsigned int alarm(unsigned int seconds)
//定时seconds发送信号SIGALRM给当前进程,默认动作是终止,返回值是上次定时剩余时间int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)//循环定时发送SIGALRM
@param: which ITIMER_REAL 表示以真实逝去的时间递减发送SIGALRM信号 new_value 结构体指针,设定定时参数 old_value 旧定时参数 一般给NULLstruct itimerval{struct timerval it_interval; //闹钟触发周期 后续产生信号的间隔struct timerval it_value; //闹钟触发时间 第一次产生信号的时间
};
struct timerval{time_t tv_sec; //ssuseconds tv_usec; //us
};
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>void handler(int signum, siginfo_t *info, void * data)
{if(signum == SIGALRM){printf("my pid:%d,i caught SIGALRM\n",getpid());}if(data != NULL){printf("signal passed data:%d\n",info->si_int);}}int main()
{struct sigaction act;
// act.sa_handler = handler; //normal signal
// act.sa_flags = 0;
// sigemptyset(&act.sa_mask); act.sa_sigaction = handler; //with data from signalact.sa_flags = SA_SIGINFO;sigaction(SIGALRM,&act,NULL);struct itimerval newvalue;newvalue.it_value.tv_sec = 2;newvalue.it_value.tv_usec = 0;newvalue.it_interval.tv_sec = 2;newvalue.it_interval.tv_usec = 0;setitimer(ITIMER_REAL,&newvalue,NULL);while(1); return 0;
}