信号灯也叫信号量,它能够用来同步进程的动作,不能传输数据。它的应用场景就像红绿灯,控制各进程使用共享资源的顺序。Posix无名信号灯用于线程同步, Posix有名信号灯,System V 信号灯。本章讨论用于进程同步的System V信号灯。
信号灯相当于一个值大于或等于0计数器,信号灯值大于0,进程就可以申请资源,信号灯值-1,如果信号灯值为0,一个进程还想对它进行-1,那么这个进程就会阻塞,直到信号灯值大于1。
使用System V信号灯的步骤如下:
1.使用semget()创建或打开一个信号灯集。
2.使用semctl()初始化信号灯集,。
3.使用semop()操作信号灯值,即进行 P/V操作。
P操作:申请资源,申清完后信号灯值-1;
V操作:释放资源,释放资源后信号灯值+1;
常用的API如下:
semget()
:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
函数功能:创建新信号灯集或获取一个创建好的信号灯集。
参数含义:
key:通过ftok()获取的key值;
nsems:指定信号灯集合中信号灯的数量;
semflg:权限掩码,指定IPC_CREAT为如果没有则创建该信号灯集,指定IPC_EXCL为如果存 在返回错误。
返回值:成功返回信号灯集标识符id,失败返回-1,
semctl()
:初始化信号灯集合
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
参数含义:
semid:信号灯集标识符;
semnum:要操作的集合中的信号灯编号
cmd:执行的操作 SETVAL IPC_RMID
arg :cmd指定SETVAL命令后由此参数向集合中的单个信号量赋值。
semop()
:在信号量上执行一个或多个操作。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
参数含义:
semid:信号灯集标识符;
sops:指向数组的指针,数组成员是对信号灯操作的结构体;
struct sembuf {
unsigned short sem_num; /信号量索引/
short sem_op; /* 执行的操作,+1或-1 /
short sem_flg; / ,阻塞0,不阻塞IPC_NOWAIT */
}
nsops:数组大小,最小是1。
返回值:成功返回0,失败返回-1。
实验代码在shm_sem/目录下:路径为:11_Linux系统开发进阶\Linux系统编程_章节使用资料。
write进程向共享内存写数据,read进程从共享内存读数据,使用信号灯同步,先写再读,read进程读完阻塞,等待write进程将S_READ信号进行V操作。
信号灯相关函数在“sem.h”:
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#define S_READ 0
#define S_WRITE 1
union semun
{
int value;
};
/*声明两种操作方式*/
struct sembuf sop[2];
/*
* P操作,申请资源,信号量值-1
*/
void p_ipc_sem(int index,int semid)
{
sop[0].sem_num = index;
sop[0].sem_op = -1;//信号量值-1
sop[0].sem_flg = 0;//阻塞
semop(semid,&sop[0],1);
}
/*
* V操作,释放资源,信号量值+1
*/
void v_ipc_sem(int index,int semid){
sop[1].sem_num = index;
sop[1].sem_op = 1;//信号量值+1
sop[1].sem_flg =0;//阻塞
semop(semid,&sop[1], 1);
}
sem.h文件中union semun用于初始化信号量,p_ipc_sem和v_ipc_sem用于P/V操作。
向共享内存写数据的程序在write.c中:
#include "sem.h"
int main(int argc, char const *argv[])
{
int ret;
key_t key;
int sem_id;
int shmid;
union semun u_u;
char *shmaddr;
key = ftok("./",808);
if(key == -1){
perror("ftok");
return -1;
}
sem_id= semget(key,2,IPC_CREAT | 0666);//申请两个信号量
if(sem_id < 0){
perror("semget");
return -2;
}
/*
* 初始化信号量
*/
u_u.value = 0;
ret = semctl(sem_id,S_READ,SETVAL,u_u);
if (ret < 0)
{
perror("semctl");
return -3;
}
u_u.value = 1;
ret = semctl(sem_id,S_WRITE,SETVAL,u_u);
if (ret < 0)
{
perror("semctl");
return -3;
}
shmid = shmget(key,256,IPC_CREAT|0666); //申请共享内存
shmaddr = (char *)shmat(shmid, NULL, 0); //映射到程序的地址空间
while(1){
//P操作:
//通过信号量,读进程释放资源,索引为S_WRITE的信号量值+1,
//写进程才能获取资源,S_WRITE信号量值-1,
//这里的资源是指共享内存。
p_ipc_sem(S_WRITE,sem_id);
printf("写进程PID<%d>输入:--->\n",getpid());
fgets(shmaddr,32,stdin);//从终端获取数据并写入共享内存
//V操作:
//写进程释放资源S_READ+1,读进程才能拿到资源,然后读进程S_READ-1。
v_ipc_sem(S_READ,sem_id);
}
return 0;
}
读进程在read.c中:
#include "sem.h"
int main(int argc, const char *argv[])
{
int ret;
key_t key;
int sem_id;
key = ftok("./",808);
if(key == -1){
perror("ftok");
return -1;
}
sem_id= semget(key,2,IPC_CREAT | 0666);
if(sem_id < 0){
perror("semget");
return -2;
}
int shmid;
shmid = shmget(key,256,IPC_CREAT|0666); //申请共享内存
char *shmaddr;
shmaddr = (char *)shmat(shmid, NULL, 0); //映射到程序的地址空间
while(1){
p_ipc_sem(S_READ,sem_id);
printf("读进程PID<%d>接收到:%s\n",getpid(),shmaddr);
v_ipc_sem(S_WRITE,sem_id);
}
return 0;
}
执行make编译:
使用两个终端进行实验,一个运行写进程,一个运行读进程,在写进程输入数据,效果如下:
发现能够读出写到共享内存的数据,使用“ipcs”命令观察IPC对象:
信号灯和共享内存的简单用法介绍到这里。