信号量也是三种XSI IPC之一,不过与消息队列和共享内存不同的是作为临界资源的信号量是以另一种临界资源的计数器的面目出现的。其基本作用机制为,为某种临界资源设定一定数量的信号量,当某个进程占用一份临界资源时信号量就减少相应的数值,当信号量为0时没有得到临界资源的进程就暂时性的进入“休眠”状态,待占用资源的进程将资源还回去后信号量增加,其他进程被唤醒,就又可以使用临界资源,这样就实现了临界资源的同步互斥。
首先介绍创建与访问信号量的函数:
int semget(key_t key, int nsems, int semflg);
返回值:成功返回该信号集的标识码,失败返回-1。
参数:
key: 信号集的名字 。
nsems: 信号集中信号量的个数。
semflg: 由九个权限标志构成,它们的⽤用法和创建⽂文件时使⽤用的mode模式标志是⼀样的。
控制函数(常用于初始化和删除一个信号量):
int semctl(int semid, int semnum, int cmd, …);
返回值:成功返回0,失败返回-1。
参数:
semid: 由semget返回的信号集标识码。
semnum: 信号集中信号量的序号。
cmd:将要采取的动作,下图(来自UNIX环境高级编程)是cmd可命令,初始化信号量用SETVAL命令,删除信号量用IPC_RMID命令。
最后⼀一个参数根据命令不同⽽而不同,如果要初始化一个信号量,则要用到一个结构:
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
进行初始化时除了第三个参数为SETVAL外,第四个参数必须是这个联合的一个对象,并且要对联合中的val赋予准备初始化的数值。
信号量操作函数:
int semop(int semid, struct sembuf *sops, unsigned nsops);
返回值:成功返回0,失败返回-1。
参数:
semid: 是该信号量的标识码,也就是semget函数的返回值。
sops: 是个指向⼀一个结构数值的指针。
nsops: 信号量的个数。
对信号量进行操作时必须要用到一个结构体:
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信号量的编号。
sem_op是信号量⼀一次PV操作时加减的数值,⼀一般只会⽤用到两个值:一个是“-1”,也就是P操作,等待信号量变得可⽤用,另⼀一个是“+1”,也就是我们的V操作,发出信号量已经变得可⽤。
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO。
最常用的是二元信号量,也叫双态信号量,它控制着单个资源,初始值为1,当一个进程占用资源后信号量即变为0,其他进程只能等待信号量变回1。用代码实现二元信号量:
当两个进程同时向显示器进行输出时,显示器也是就是一种临界资源。我们用两个进程同时向屏幕打印字符(子进程打印字符A,父进程打印字符B),如果不作任何处理,那么打印的效果将是这样的:
而我们要的效果是这样的:
即每个进程打印的字符是两个两个挨着的,现在用代码来实现:
Makefile文件:
.PHONY : all
all : test
test : test.c comm.c
gcc -o test test.c comm.c
.PHONY : clean
clean :
rm -f test
comm.h文件:
#ifndef __COMM_H_
#define __COMM_H_
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
int CreatSem(int nsems);
int GetSem(int nsems);
int InitSem(int semid, int nums, int initval);
int DestorySem(int semid);
int P(int semid, int who);
int V(int semid, int who);
#endif //__COMM_H_
comm.c文件:
#include "comm.h"
static int _GetCommSem(int nsems, int flag)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0)
{
write(1, "ftok\n", strlen("ftok\n"));
return -1;
}
int semid = semget(key, nsems, flag);
if(semid < 0)
{
write(1, "semget\n", strlen("semget\n"));
return -1;
}
return semid;
}
int CreatSem(int nsems)
{
return _GetCommSem(nsems, IPC_CREAT|IPC_EXCL|0666);
}
int GetSem(int nsems)
{
return _GetCommSem(nsems, IPC_CREAT);
}
int InitSem(int semid, int nums, int initval)
{
union semun un;
un.val = initval;
if(semctl(semid, nums, SETVAL, un) < 0)
{
write(1, "semctl\n", strlen("semctl\n"));
return -1;
}
}
int DestorySem(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
write(1, "semctl\n", strlen("semctl\n"));
return -1;
}
return 0;
}
static int CommPV(int semid, int who, int op)
{
struct sembuf sf;
sf.sem_num = who;
sf.sem_op = op;
sf.sem_flg = 0;
if(semop(semid, &sf, 1) < 0)
{
write(1, "semop\n", strlen("semop\n"));
return -1;
}
return 0;
}
int P(int semid, int who)
{
return CommPV(semid, who, -1);
}
int V(int semid, int who)
{
return CommPV(semid, who, 1);
}
test.c文件:
#include "comm.h"
int main()
{
int semid = CreatSem(1);
InitSem(semid, 0, 1);
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{//child
int chsemid = GetSem(0);
int i = 0;
while(i++ < 200)
{
P(chsemid, 0);
write(1, "A", strlen("A"));
//write(1, "A ", strlen("A "));
printf("A");
//fflush(stdout);
usleep(10000);
//printf("A");
write(1, "A ", strlen("A "));
//fflush(stdout);
usleep(10000);
V(chsemid, 0);
}
}
else
{//father
int i = 0;
while(i++ < 200)
{
P(semid, 0);
write(1, "B", strlen("B"));
//write(1, "B ", strlen("B "));
//printf("B");
//fflush(stdout);
usleep(10000);
//printf("B ");
write(1, "B ", strlen("B "));
//fflush(stdout);
usleep(10000);
V(semid, 0);
}
wait(NULL);
}
DestorySem(semid);
return 0;
}
这是一个信号量的简单应用,我们知道共享内存IPC不具备同步互斥功能,但它又是最快的IPC,我们可以在共享内存上加入信号量,这样就实现即快速又安全的进程间通信,在这里就不实现了,如果有时间后面实现了再发出来。