一、信号灯概念
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
初始化
P操作(申请资源)
V操作(释放资源)
P(S) 含义如下:
if (信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
}
else { 申请资源的任务阻塞;}
V(S) 含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}
三种信号灯:
Posix 有名信号灯
Posix 无名信号灯 (linux只支持线程同步)
System V 信号灯
信号灯的含义
Posix 信号灯:计数信号灯
System V 信号灯:一个或多个计数信号灯的集合
可同时操作集合中的多个信号灯
信号灯文件位置:/dev/shm
信号灯P操作
int sem_wait(sem_t *sem);
获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取
信号灯V操作
int sem_post(sem_t *sem);
释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回
注意:编译posix信号灯需要加pthread动态库。
二、有名信号灯
有名信号灯打开
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,普通表示资源数目
有名信号灯关闭
int sem_close(sem_t *sem);
有名信号灯的删除
int sem_unlink(const char* name);
代码演示
生产者等待 sem_w
信号量,然后从标准输入读取一行文本并存储到共享内存中,最后释放 sem_r
信号量以通知消费者。消费者等待 sem_r
信号量,然后读取共享内存中的数据并释放 sem_w
信号量以通知生产者可以继续写入数据。这样可以确保生产者和消费者之间的同步,并避免竞争条件。
读
#include <fcntl.h>
#include <sys/stat.h>
#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){ //信号处理函数,接受sig信号编号
sem_unlink("mysem_r"); //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; //初始化,指向信号处理函数delsemfile
act.sa_flags = 0; //初始化
sigemptyset(&act.sa_mask); //清空信号屏蔽集
sigaction(SIGINT,&act,NULL); //接收到SIGINT信号时调用act中的信号处理函数delsemfile
key = ftok(".",100); //生成一个键值
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT); //创建一个大小为 500 字节、权限为 0666 的
if(shmid<0){ //共享内存段,并将其标识符赋值给变量shmid
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0); //将共享内存段附加到进程的地址空间中,
//并将其地址赋值给shmaddr
//创建两个信号量
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); //等待sem_r信号量
printf("%s\n",shmaddr);
sem_post(sem_w); //释放sem_w信号量
}
}
写
#include <fcntl.h>
#include <sys/stat.h>
#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);
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);
}
}
三、无名信号灯
无名信号灯初始化
int sem_init(sem_t *sem, int shared, unsigned int value);
参数:
sem:需要初始化的信号灯变量
shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。
Value:信号量的值
无名信号灯销毁
int sem_destroy(sem_t* sem);
代码演示
#include <fcntl.h>
#include <sys/stat.h>
#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){ //信号处理函数,接收到SIGINT 信号时销毁两个信号量并退出程序
// sem_unlink("mysem_w");
sem_destroy(&sem_r);
sem_destroy(&sem_w);
exit(0);
}
void *readmem(void *arg){ //线程函数,在新线程中执行
while(1){
sem_wait(&sem_r); //等待sem_r信号量
printf("%s\n",shmaddr); //打印共享内存中的内容
sem_post(&sem_w); //释放 sem_w 信号量
}
}
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); //接收到 SIGINT 信号时调用 destroysem 函数。
key = ftok(".",100); //生成一个键值
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT); //调用shmget,来创建或获取一个大小为
//500 字节、权限为 0666 的共享内存段,
//并将其标识符赋值给变量 shmid
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0);
sem_init(&sem_r,0,0); //初始化0
sem_init(&sem_w,0,1); //初始化1
pthread_t tid; //存储新线程的标识符
pthread_create(&tid,NULL,readmem,NULL); //创建一个新线程,并指定其运行函数为前面定
//义的 readmem 函数。
while(1){
sem_wait(&sem_w); //在主线程中,在循环中,程序等待sem_w信号量
printf(">");
fgets(shmaddr,500,stdin); //标准输入读取文本并存储在共享内存中
sem_post(&sem_r); //释放
}
}
四、System V 信号灯
#include<sys/ipc.h>
#include<sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值(和信号灯关联的key值)
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值: 成功:信号灯集ID ;
失败:-1
#include<sys/ipc.h>
#include<sys/sem.h>
int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
semnum: 要操作的集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,需要用到第四个参数:共用体
IPC_RMID:从系统中删除信号灯集合
返回值:成功 0 ; 失败 -1
示例:要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0
union semun myun;
myun.val=2; //第一个初值为2
if (semctl(semid, 0, SETVAL, myun)<0){ //0——第一个信号灯
perror("semctl");
exit(-1);
}
myun.val=0; //第二个初值为0
if (semctl(semid, 1, SETVAL, myun)<0){ //0——第二个信号灯
perror("semctl");
exit(-1);
}
#include<sys/ipc.h>
#include<sys/sem.h>
int semop ( int semid, struct sembuf *sops, size_t nsops);
功能:对信号灯集合中的信号量进行P - V操作
参数:semid:信号灯集ID
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 1 : 释放资源,V操作 // -1 : 分配资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT(立刻返回), SEM_UNDO
}; //对某一个信号灯的操作;如果同时对多个操作,则需要定义这种结构体数组
nsops: 要操作的信号灯的个数
返回值:成功 :0 ; 失败:-1
示例:
要求:父子进程通过SystemV信号灯同步对共享内存的读写
父进程从键盘输入字符串到共享内存
子进程删除字符串中的空格并打印
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.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
#define WRITE 1
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
viod init_sem(int semid,int s[],int n) //
{
int i;
union semun myun;
for(i=0;i<n;i++){
myun.val = s[i];
semctl(semid,i,SETVAL,myun);
}
}
viod pv(int semid,int num,int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid,&buf,1);
}
int main(){
int shmid,semid,s[]={0,1};
pid_t = pid;
key_t key;
char *shmaddr;
if((key = ftok(".",'s')) == -1)
{
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 = (char *)shmat(shmid,NULL,0)) == (char *)-1)
{
perror("shmat");
goto _error2;
}
if((pid = fork()) < 0)
{
perror("fork");
goto _error2;
}
else if(pid == 0)
{
char *q,*p;
while(1)
{
pv(semid,READ,-1);
p = q = shmaddr;
while(*q)
{
if(*q !=' ')
{
*p++ = *q;
}
q++;
}
*p = '\0';
printf("%s",shmaddr);
pv(semid,WRITE,1);
}
}
else{
while(1)
{
pv(semid,WRITE,-1);
printf("input >");
fgets(shmaddr,N,stdin);
if(strcpy(shmaddr,"quit\n") == 0)
break;
pv(semid,READ,1);
}
kill(pid,SIGUSR1);
}
_error2:
semctl(semid,0,IPC_RMID)
_error1:
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
五、作业
编程实现无名信号灯
见前文