现在已经写了两种进程间通信方式
Linux--进程间通信之匿名管道及命名管道:http://blog.csdn.net/sayhello_world/article/details/59556670
Linux--进程间通信之消息队列:http://blog.csdn.net/sayhello_world/article/details/59690231
今天我们来说第三种进程间通信方式--信号量。
一.什么是信号量?
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
信号量的值为正的时候,说明它空闲。所测试的线程可以锁定使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
二.为什么要有信号量?
当我们进行进程间通信的时候,若写端向其写hello world,读端读取,则很有可能写端还未写完hello world,读端就已经读取了。这样子便会导致数据不一致问题,为了避免这一问题,便有了信号量。
介绍几个概念:
原子性:通俗的来说就是一件事要么做了,要么没做,如果他做了一定是做完了。
临界资源:不同的进程看到共同的资源。
临界区:共同访问临界资源的那段代码。
所以这里的保护机制,保护的是临界区,因为我们只能控制代码。
三.信号量两种操作
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂
起,就给它加1.
那么我们可以知道,PV操作为原子性的,二元信号量本身就为一把锁。
同步:访问临界资源并以特定的顺序访问。(一般同步都在互斥的情况下)
互斥:在任意时刻只允许一个人进入临界区访问临界资源。
饥饿:长时间没有得到资源。
那么就会有一个问题:
如果有一个全局变量,他能让两个进程同时看到,它能取代信号量吗?
不能,因为变量开始存于内存中,但是加减运算在CPU中,若要给此变量加减1,则应该先将他取到CPU中,CPU中加一,再返给内存中,需要三步。任意一步都可能会被中断,所以与另一进程不具有互斥性。
那么我们来实现一下信号量:
思路:两个进程两行打印输出A和B,如果没有信号量之前,则可能是AB可能是乱序输出,如果加了PV操作,则应该是成对输出。
需要用到的函数:
信号量的创建:
int semget(key_t key,int nsems,int semflg)
key:与消息队列中key值含义相同,用ftok生成
nsems:信号量特有的参数,在system V下申请信号量是以信号量集的方式,次变量表示信号量的个数是多少个。
semflg:与消息队列中相同,两个参数IPC_CREAT与IPC_EXCL
详情见:http://blog.csdn.net/sayhello_world/article/details/59690231
信号量的获取:
与消息队列创建相同,只是semflg传的参数不同,获取时只需传IPC_CREAT
信号量的销毁:
int semctl(int semid,int semnum,int cmd,.....)
返回值:失败返回-1
semnum:操作信号量集中的哪一个信号量
cmd:销毁时用IPC_RMID
信号量的初始化:
与信号量的销毁相同,只是这时传的值不同。
semctl(semid,which,SETVAL,un)
这里un为一个结构体如下
union semun {
int val; // 使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
PV操作:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
函数参数:第一个参数,sem_id,是由semget函数所返回的信号量标识符。
第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0,一般从0,1,...num_secs-1
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, 一个是+1,即V(释放信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};
着重说一下第三个成员:sem_flg:通常设置为SEM_UNDO,这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。
第三个参数num_sem_ops,表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。
函数返回值:成功:返回信号量集的标识符,错误,返回-1
comm.h
#ifndef __COMM_H__
#define __COMM_H__
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<unistd.h>
#include<string.h>
#define PATHNAME "."
#define PROJ_ID 0X666
typedef 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) */
}_semun;
int create_sem(int semnum);
int get_sem();
int init_sem(int semid,int which,int _val);
int destroy_sem(int semid);
int P(int semid,int which);
int V(int semid,int which);
#endif
comm.c
#include"comm.h"
static int comm_sem(int semnum,int flags)
{
key_t k = ftok(PATHNAME,PROJ_ID);
int semid = semget(k,semnum,flags);
if(semid < 0)
{
perror("semget\n");
return -1;
}
return semid;
}
int creat_sem(int semnum)
{
return comm_sem(semnum,IPC_CREAT|IPC_EXCL|0666);
}
int get_sem()
{
return comm_sem(0,IPC_CREAT);
}
int destroy_sem(int semid)
{
if(semctl(semid,0,IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
int init_sem(int semid,int which,int _val)
{
_semun un;
un.val = _val;
if(semctl(semid,which,SETVAL,un) < 0)
{
perror("init_sem");
return -1;
}
return 0;
}
static int comm_op(int semid,int which,int op)
{
struct sembuf sbuf;
sbuf.sem_num = which;
sbuf.sem_op = op;
sbuf.sem_flg = 0;
return semop(semid,&sbuf,1);
}
int P(int semid,int which)
{
return comm_op(semid,which,-1);
}
int V(int semid,int which)
{
return comm_op(semid,which,1);
}
测试代码:
comm_test.c
#include"comm.h"
int main()
{
int semid = creat_sem(1);
init_sem(semid,0,1);
int status = 0;
pid_t id = fork();
if(id< 0){
perror("fork");
return -1;
}
else if(id == 0){//child
while(1)
{
int _semid = get_sem();
P(_semid,0);
printf("A");
usleep(12345);
fflush(stdout);
printf("A");
usleep(55787);
fflush(stdout);
V(_semid,0);
}
exit(1);
}else{//father
while(1)
{
P(semid,0);
printf("B");
usleep(16745);
fflush(stdout);
printf("B");
usleep(59887);
fflush(stdout);
V(semid,0);
}
wait(&status);
destroy_sem(semid);
}
return 0;
}
未添加信号量之前:有单个出现的
用信号量之后:都是成双出现的