目录
掌握:信号灯机制、信号灯初始化、打开/创建信号灯、信号灯-P操作、信号灯-V操作、信号灯、共享内存
1 信号量/灯(semaphore)基本概念
信号量代表某一类资源,其值表示系统中该资源的数量。
概念:是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的生产者和消费者场景。
类似之前讲过的条件变量。
信号量是一个受保护的变量,只能通过三种操作来访问
- 初始化
- P操作(申请资源)
- V操作(释放资源)
2 信号量-P/V操作概念
P(S) 含义如下:
if (信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
}
else { 申请资源的任务阻塞;}
V(S) 含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}
3 三种信号灯
信号灯也叫信号量,用于进程/线程同步或互斥的机制
信号灯的类型
- Posix 无名信号灯
- Posix有名信号灯 (linux只支持线程同步)
- System V 信号灯
Posix 有名信号灯和无名信号灯使用:
wait 相当于p操作,post相当于v操作
3.1 有名信号灯
3.1.1 打开
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数:
name:name是给信号灯起的名字
oflag:打开方式,常用O_CREAT
mode:文件权限。常用0666
value:信号量值。二元信号灯值为1,普通表示资源数目
信号灯文件位置:/dev/shm
3.1.2 关闭
int sem_close(sem_t *sem);
3.1.3 删除
int sem_unlink(const char* name);
3.2 无名信号灯
3.2.1 初始化
int sem_init(sem_t *sem, int shared, unsigned int value);
参数:
sem:需要初始化的信号灯变量
shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。0 – 线程间 1 – 进程间
Value:信号量的值
3.2.2 销毁
int sem_destroy(sem_t* sem);
成功时返回0,失败时返回EOF
sem 指向要操作的信号量对象
3.3 信号灯P操作
int sem_wait(sem_t *sem);
成功时返回0,失败时返回EOF
sem 指向要操作的信号量对象
获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取
3.4 信号灯V操作
int sem_post(sem_t *sem);
释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回
注意:编译posix信号灯需要加pthread动态库。
3.5 示例
3.5.1 有名信号示例
避免读写混乱:规定写资源初值为1,读资源初值为0。获取写入权限,后写-1,读+1,获取读权限后,读-1,写+1。
sem_r.c
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
//删除信号,以免程序退出(ctrl+c) 重新打开无法开启
void delsemfile(int sig){
sem_unlink("mysem_r");
exit(0);
}
int main(){
sem_t *sem_r,*sem_w;
key_t key;
int shmid;
char *shmaddr;
struct sigaction act;
act.sa_handler = delsemfile;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL); //启用linux中信号退出回调函数delsemfile,用于删除信号量
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT); //创建信号灯
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0); //创建共享内存
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0); //创建初值为0的读信号灯
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1); //创建初值为1的写信号灯
while(1){
sem_wait(sem_r); //P操作,获取读资源,读资源-1
printf("%s\n",shmaddr);
sem_post(sem_w); //V操作,写资源+1
}
}
sem_w.c
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
void delsemfile(int sig){
sem_unlink("mysem_w");
exit(0);
}
int main(){
sem_t *sem_r,*sem_w;
key_t key;
int shmid;
char *shmaddr;
struct sigaction act;
act.sa_handler = delsemfile;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT); //创建信号灯,500个字节
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0);
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
while(1){
sem_wait(sem_w);
printf(">");
fgets(shmaddr,500,stdin);
sem_post(sem_r);
}
}
3.5.2 无名信号灯示例(逻辑同上)
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>
sem_t sem_r,sem_w;
char *shmaddr;
void destroysem(int sig){
// sem_unlink("mysem_w");
sem_destroy(&sem_r);
sem_destroy(&sem_w);
exit(0);
}
void *readmem(void *arg){
while(1){
sem_wait(&sem_r); //P操作
printf("%s\n",shmaddr);
sem_post(&sem_w); //V操作
}
}
int main(){
key_t key;
int shmid;
struct sigaction act;
act.sa_handler = destroysem;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0);
// sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
// sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
sem_init(&sem_r,0,0); //有名信号灯创建,读初值0
sem_init(&sem_w,0,1); //有名信号灯创建,写初值1
pthread_t tid;
pthread_create(&tid,NULL,readmem,NULL); //创建线程
while(1){
sem_wait(&sem_w); //P操作
printf(">");
fgets(shmaddr,500,stdin);
sem_post(&sem_r); //V操作
}
}
3.5 System V 信号灯使用:
3.5.1 创建/打开信号灯
int semget(key_t key, int nsems, int semflg);
参数:key:ftok产生的key值(和信号灯关联的key值)
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT |0666 IPC_EXCL
返回值:成功:信号灯集ID ; 失败:-1
3.5.2 进行P - V操作
int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行P - V操作
参数:semid:信号灯集ID
opsptr: 操作结构体
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};//对某一个信号灯的操作,如果同时对多个操作,则需要定义这种结构体数组
nops: 要操作的信号灯的个数 ,1个
返回值:成功 :0 ; 失败:-1
3.5.3 初始化/删除
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
semnum: 要操作的集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,需要用到第四个参数:共用体
IPC_RMID:从系统中删除信号灯集合
返回值:成功 0 ; 失败 -1
3.5.4 System V IPC - 信号灯特点
System V 信号灯是一个或多个计数信号灯的集合
可同时操作集合中的多个信号灯
申请多个资源时避免死锁
3.5.5 System V信号灯使用步骤
- 打开/创建信号灯
- semget 信号灯初始化
- semctl P/V操作 semop
- 删除信号灯 semctl
3.6 System V信号灯示例
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#define SEM_READ 0
#define SEM_WRITE 1
union semun {
int val;
};
void Poperation(int semid,int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
void Voperation(int semid,int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
int main(){
key_t key;
char *shmaddr;
int semid,shmid;
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
semid = semget(key,2,IPC_CREAT |0666);
if(semid<0){
perror("semget");
return 0;
}
shmid = shmget(key,500,IPC_CREAT |0666);
shmaddr = shmat(shmid,NULL,0);
union semun mysem;
mysem.val = 0;
semctl(semid,SEM_READ,SETVAL,mysem);
mysem.val = 1;
semctl(semid,SEM_WRITE,SETVAL,mysem);
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
shmctl(shmid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID);
exit(-1);
}else if(pid == 0){
while(1){
Poperation(semid,SEM_READ);
printf("%s\n",shmaddr);
Voperation(semid,SEM_WRITE);
}
}else{
while(1){
Poperation(semid,SEM_WRITE);
printf(">");
fgets(shmaddr,32,stdin);
Voperation(semid,SEM_READ);
}
}
}