文章目录
1,System V IPC - 信号灯
- 信号灯也叫信号量,用于进程/线程同步或互斥的机制
- 信号灯的类型
·Posix 无名信号灯
·Posix有名信号灯
·System V 信号灯 - 信号灯的含义
·计数信号灯(信号灯的值就是他代表的资源的数量,Posix 无名信号灯/Posix有名信号灯,都是计数信号灯)
2,System V IPC - 信号灯特点
- System V 信号灯是一个或多个计数信号灯的集合
- 可同时操作集合中的多个信号灯
- 申请多个资源时避免死锁(信号灯在一个集合里,需要的资源同时申请,都满足时资源才能申请到)
3,System V信号灯使用步骤
- 打开/创建信号灯 semget
- 信号灯初始化 semctl
- P/V操作 semop
- 删除信号灯 semctl
3.1,信号灯创建/打开 semget()
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
- 成功时返回信号灯的id,失败时返回-1
- key 和信号灯关联的key IPC_PRIVATE 或 ftok
- nsems 集合中包含的计数信号灯个数
- semflg 标志位 IPC_CREAT|0666 IPC_EXCL(通常和IPC_CREAT一起使用,加上IPC_EXCL,如果信号灯存在,则报错)
信号灯在使用之前必须初始化,且只能初始化一次,通常第一个进程初始化,后面的进程就不能初始化了。
if((semid = semget(key,3,IPC_CREAT|0666|IPC_EXCL)) < 0)
{
if(errno == EEXIST)
{
semid = semget(key,3,IPC_CREAT|0666)
}
}
else
{
初始化信号灯集;
}
3.2,信号灯初始化 semctl()
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …);
- 成功时返回0,失败时返回EOF
- semid 要操作的信号灯集id
- semnum 要操作的集合中的信号灯编号
- cmd 执行的操作 SETVAL(设置信号灯值) IPC_RMID(删除整个信号灯集)
- union semun 取决于cmd(SETVAL需要用到第四个参数,IPC_RMID不需要第四个参数),该共用体需要用户自己定义
系统中union semun 共用体参考
union semun {union semun {
int val; /* Value for SETVAL *///信号量初始值
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
3.2.1,信号灯初始化—示例
要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0
union semun myun;
myun.val = 2;
if (semctl(semid, 0, SETVAL, myun) < 0)
{
perror(“semctl”); exit(-1);
}
myun.val = 0;
if (semctl(semid, 1, SETVAL, myun) < 0)
{
perror(“semctl”); exit(-1);
}
3.3,信号灯P/V操作 semop()
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
- 成功时返回0,失败时返回-1
- semid 要操作的信号灯集id
- sops 描述对信号灯操作的结构体(数组)
- nsops 要操作的信号灯的个数(一次操作sops数组里的前nsops个元素)
3.3.1,信号灯操作 sembuf结构体定义
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
};
- semnum 信号灯编号
- sem_op -1:P操作 1:V操作 (也可以让信号数量增加或减少多个,将“1”换成“n”)
- sem_flg 0(阻塞方式) / IPC_NOWAIT
假设当前信号灯集合中有3个信号灯(意味着最多可以同时操作3个信号灯),编号:0,1,2
现在同时对编号为0和2的信号灯进程P操作
struct sembuf buf[3];//结构体数组的大小应该和信号灯集中信号灯的数目保持一致
buf[0].sem_num = 0;
buf[0].sem_op = -1;
buf[0].sem_flg = 0;
buf[1].sem_num = 2;
buf[1].sem_op = -1;
buf[1].sem_flg = 0;
semop(semid,&buf,2);//2表示依次取出buf中的前两个元素进行操作
4,信号灯集/共享内存—示例
要求:父子进程通过System V信号灯同步对共享内存的读写
·父进程从键盘输入字符串到共享内存
·子进程删除字符串中的空格并打印
·父进程输入quit后删除共享内存和信号灯集,程序结束
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define N 64 //共享内存大小
#define READ 0 //可读缓冲区(个数)信号灯在信号灯集合中的编号为0
#define WRITE 1 //可写缓冲区(个数)信号灯在信号灯集合中的编号为1
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo * __buf;
};
void init_sem(int semid,int s[],int n);
void sem_pv(int semid,int num,int op);
int main(int argc, const char *argv[])
{
int shmid,semid,s[]={0,1};
pid_t pid;
key_t key;
char *shmaddr;
if((key = ftok(".",'s')) == -1)//生成KEY值
{
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,N,IPC_CREAT|0666)) < 0)//申请共享内存
{
perror("shmget");
exit(-1);
}
if((semid = semget(key,2,IPC_CREAT|0666)) < 0)//申请信号灯集
{
perror("semget");
goto _error1;//申请失败,释放申请的共享内存,程序退出
}
init_sem(semid,s,2);//信号灯集初始化
if((shmaddr = shmat(shmid,NULL,0)) == (char *)-1)//映射共享内存地址
{
perror("shmat");
goto _error2;//映射失败,释放申请的信号灯集,释放申请的共享内存,程序退出
}
if((pid = fork()) < 0)//创建子进程
{
perror("fork");
goto _error2;
}
else if(pid == 0)
{
char *p,*q;
while(1)
{
sem_pv(semid,READ,-1);//对读信号灯进行P操作
p = q = shmaddr;
while(*q)
{
if(*q != ' ')
{
*p++ = *q;
}
q++;
}
*p = '\0';
printf("%s",shmaddr);
sem_pv(semid,WRITE,1);//对写信号灯进行V操作
}
}
else
{
while(1)
{
sem_pv(semid,WRITE,-1);//对写信号灯进行P操作
printf("input > ");
fgets(shmaddr,N,stdin);
if(strcmp(shmaddr,"quite\n") == 0)break;
sem_pv(semid,READ,1);//对读信号灯进行V操作
}
kill(pid,SIGUSR1);//给子进程发信号,让其结束
}
_error2:
semctl(semid,0,IPC_RMID);//释放申请的信号灯集
_error1:
shmctl(shmid,IPC_RMID,NULL);//释放申请的共享内存
return 0;
}
void init_sem(int semid,int s[],int n)//信号灯集初始化函数
{
int i;
union semun myyun;
for(i=0;i<n;i++)
{
myyun.val = s[i];//读信号灯值初始化为0,写信号灯值初始化为1
semctl(semid,i,SETVAL,myyun);
}
}
void sem_pv(int semid,int num,int op)//信号灯集P/V操作函数
{
struct sembuf buf;
buf.sem_num = num;//信号灯编号
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid,&buf,1);
}