您的位置:首页 > 其它

进程间通信----信号量

2018-03-27 18:15 281 查看
进程间通信----信号量

Linux中的内存空间分为系统空间和用户空间。
在系统空间中:由于各个线程的地址空间是共享的,即一个线程可以随意访问 kernel中的任意地址,所以无需进程通信机制的保护。
而在用户空间中:每个进程都有自己的地址空间,一个进程要和另外一个进程通信,必须陷入到有足够权限访问其他进程的kernel中,从而与其他进程通信。
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。(信号量是用来调协进程对共享资源的访问的。)信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。信号量的基本原理

P(sv)--->申请资源(s--):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv)--->释放资源(s++):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
☆☆☆☆☆☆linux提供了两种信号量内核信号量,由内核控制路径使用用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量
POSIX信号量又分为有名信号量和无名信号量
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。
临界资源:一次只允许一个进程(一个线程)使用的资源叫做临界资源。
临界区:访问临界资源的代码称为临界区
互斥:指某一资源同时只允许一个访问者对其进行访问。
原子性:一个事务包含多个操作,这些操作要么全部执行,要么都不执行。
同步: 基本上都是以互斥为条件,让不同的进程访问临界资源,以某种特定的顺序去访问。

Linux的信号量机制

Linux中提供了一组信号量接口来对信号量进行操作,它们不只是针对二进制信号量,但是这些函数都是用来对成组的信号量进行操作的,它们的声明存在于sys/sem.h中。

☆☆☆信号量集函数:
1、ftok函数:把一个已经存在的路径名和一个整数标识得转换成一个key_t值,称为IPC键:

#include <sys/ipc.h>
#include <sys/types.h>
key_t ftok(const char* path, int id);
参数 [pathname]:通常是跟本应用用关的目录。
参数 [proj_id]:指的是本应用所用到的IPC的一个序列号,成功返回IPC键,失败返回-1。
[返回值]:成功返回键值,失败返回-1。
注:两进程如在pathname和proj_id上达成一致(或约定好),双方就都能够通过调用ftok函数得到同一个IPC键。
2、semget()函数:创建一个信号量或访问一个已经存在的信号量集。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget( key_t key, int nsems, int semflg);

参数1[key]:类似于端口号,也可以由ftok函数生成。
参数2[nsems]:在sysytem v中,申请信号量是以信号量集nsems去申请,而不是一个一个去申请,底层是一个数组。
参数3[semflg]:IPC_CREAT或IPC_EXCL。
[返回值]:是一个称为信号量标识符的整数,semop和semctl函数将使用它。
3、 semop()函数:操作一个或一组信号,也可称为PV操作。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf * sops, unsigned nsops);
参数1[semid]:信号集的id,通过semget获取。
参数2[sops]:是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示

struct sembuf
{
unsigned short sem_num;  // 在信号集中的编码 0 , 1, ... nsems-1
short  sem_op;     //操作  负值或正值
short  sem_flg;    // IPC_NOWAIT, SEM_UNDO
};

参数nsops规定sops数组元素的个数:sem_op的取值如下:
(1)若sem_op为正(V操作),这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(2)若sem_op为负(P操作),这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(3)若sem_op为0,这表示调用进程希望等待到该信号量值变成0。参数3[nsops]:信号操作结构的数量,恒大于等于1。
[返回值]:成功返回0,失败返回-1。
4、semctl()函数:初始化或移除信号量集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd, ...);
参数1[semid]:信号量集IPC标识符。
参数2[semnum]:表示信号集中的哪个信号,第一个信号为0。
参数3[cmd]:在semid指定的信号集上指向此命令,cmd常用的选择如下:

bb13
IPC_SEAT:对此集合取semid_ds 结构,并存放在由arg.buf指向的结构中。
IPC_RMID:从系统中删除该信号量集合。
SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数。
参数4[可变参数列表]:是可选的,取决于第三个参数cmd。如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union):
union semun
{
int val;
struct semid_ds * buf;
unsigned short * array;
struct seminfo * __buf;
};

[返回值]:成功返回正数,失败返回-1 。
[查看信号量]

ipcs -s

[删除信号量]

ipcrm -s [semid]

代码实现:

comm.h
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/ipc.h>
4 #include<sys/sem.h>
5 #define PATHNAME "."
6 #define PROJ_ID
7 int createSemset(int nums);
8 int initSem(int semid,int nums,int initVal);
9 int getSemSet(int nums);
10 int P(int semid,int who);
11 int V(int semid,int who);
12 int destroySemSet(int semid);
13 union semun {
14     int val;
15     struct semid_ds *buf;
16     unsigned short *array;
17     struct seminfo *_buf;
18 };
comm.c 1 #include "comm.h"
2 static int commSemSet(int nums,int flags){
3 key_t _key = ftok(PATHNAME,PROJ_ID);
4 if(_key<0){
5 perror("ftok");
6 return -1;
7 }
8 int semid = semget(_key,nums,flags);
9 if(semid<0){
10 perror("semget");
11 return -2;
12 }
13 return semid;
14 }
15 int createSemSet(int nums){
16 return commSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
17 }
18 int getSemSet(int nums){
19 return commSemSet(nums,IPC_CREAT);
20 }
21 int initSem(int semid,int nums,int initval){
22 union semun _un;
23 _un.val=initval;
24 if(semctl(semid,nums,SETVAL,_un)<0){
25 perror("semctl");
26 return -1;
27 }
28 return 0;
29 }
30 static int commPV(int semid,int who,int op){
31 struct sembuf _sf;
32 _sf.sem_num=who;
33 _sf.sem_op=op;
34 _sf.sem_flg=0;
35 if(semop(semid,&_sf,1)<0){
36 perror("semop");
37 return -1;
38 }
39 return 0;
40 }
41 int P(int semid,int who){
42 return commPV(semid,who,-1);
43 }
44 int V(int semid,int who){
45 return commPV(semid,who,1);
46 }
47 int destroySemSet(int semid){
48 if(semctl(semid,0,IPC_RMID)<0){
49 perror("semctl");
50 return -1;
51 }
52 return 0;
53 }
test.c 1 #include "comm.h"
2 #include<stdio.h>
3 #include<stdlib.h>
4 int main(){
5 int semid=createSemSet(1);
6 initSem(semid,0,1);
7 pid_t id=fork();
8 if(id==0){//child
9 int _semid = getSemSet(0);
10 while(1){
11 P(_semid,0);
12 printf("A");
13 fflush(stdout);
14 usleep(123456);//---->vs----sleep--->s
15 printf("A");
16 fflush(stdout);
17 usleep(321456);
18 V(_semid,0);
19 }
20 }
21 else{//father
22 while(1){
23 P(semid,0);
24 printf("B");
25 fflush(stdout);
26 usleep(223456);
27 printf("B");
28 fflush(stdout);
29 usleep(123456);
30 V(semid,0);
31 }
32 wait(NUll);
33
34 }
35 destroySemSet(semid);
36 return 0;
37 }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息