在學習了進程間通信的前幾種方式管道,消息隊列、共享內存之後,進程間通信的另一種方式——信號量。
在學習信號量之前,需要先理解幾個名詞。
互斥:由於各進程要求共享資源,而有些資源需要互斥使用,因此各進程間需要競爭使用這些資源,這種關係爲進程的互斥。
同步:多個進程需要相互配合共同完成一項任務。
臨界資源:系統中某些資源一次只允許一個進程使用,稱這樣的資源爲臨界資源或互斥資源。
臨界區:在進程中涉及到互斥資源的程序段叫臨界區。
1、信號量
信號量本質上就是一個計數器,用來衡量臨界資源的個數。但與此同時,信號量也是一個臨界資源。
信號量的結構體僞代碼:
struct semaphore
{
int value;
pointer_PCB queue;
}
信號量值得含義:
S>0: S表示可用資源的個數;
S=0: 表示無可用資源,無進程等待;
S<0: |S|表示等待隊列中等待進程的個數;
2、 P、V原語
信號量中最重要的就是P、V操作。
P操作:減1操作 就是向操作系統申請資源,申請成功,進行減1操作;
V操作:加1操作 就是向操作系統歸還資源,歸還成功,進行加1操作;
P、V操作均爲原子操作,在使用時,需要保證P、V操作的原子性。這也從側面說明了爲什麼不定義一個全局變量int count,而需要信號量集的出現。
3、 信號量集結構
4、信號量集函數
(1)創建信號量集的函數
int semget(key_t key, int nsems, int semflg);
參數:key:(同消息隊列和共享內存)信號量集的名字;
nems:信號量集中信號量的個數;
semflg:(同消息隊列和共享內存)和創建文件時使用的mode標誌一樣的。
IPC_CREAT|IPC_EXCL:不存在創建,存在出錯返回;IPC_CREAT:不存在創建,存在返回;
返回值:創建成功返回信號量集的標識符,創建失敗返回-1
(2)控制信號量集的函數
int semctl(int semid, int semnum, int cmd, ...);
參數:semid:semget返回的信號量集的標識符;
semnum:要對信號量集中的第幾個信號量進行操作;
cmd:將要採取的動作,不同的可取值有不同的含義。如下圖
在使用信號量集之前必須對信號量集進行初始化,相當於要把cmd設置爲SETVAL,這時候就要在加一個參數,這個參數是一個聯合體。
不過,這個聯合體中我們只使用第一個變量val。
返回值:成功返回0,失敗返回-1
(3)訪問信號量集的函數
int semop(int semid, struct sembuf *sops, unsigned nsops);
參數:semid:senget返回的信號量集的標識符;
sops:是一個指向結構數組的指針;
nsops:信號量的個數;
說明一下,這個結構體struct sembuf
5、信號量的實現
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<unistd.h>
#define PATHNAME "."
#define PROJ_ID 0X6666
int creatsem(int nums);
int initsem(int semid,int nums,int initval);
int getsem(int nums);
int p(int semid,int who);
int v(int semid,int who);
int destroysem(int semid);
#endif
test_sem.c
#include "comm.h"
union semun
{
int val;
};
int commsem(int nums, int flags)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key<0)
{
perror("ftok");
return -1;
}
int semid = semget(key, nums, flags);
if (semid<0)
{
perror("semget");
return -2;
}
return semid;
}
int creatsem(int nums)
{
return commsem(nums, IPC_CREAT | IPC_EXCL | 0666);
}
int initsem(int semid, int nums, int value)
{
union semun u;
u.val = value;
if (semctl(semid, nums, SETVAL, u)<0)
{
perror("semctl");
return -1;
}
return 0;
}
int getsem(int nums)
{
return commsem(nums, IPC_CREAT);
}
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)
{
perror("semop");
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);
}
int destroyaem(int semid)
{
if (semctl(semid, 0, IPC_RMID)<0)
{
perror("semctl");
return -1;
}
}
int main()
{
int semid = creatsem(1);
initsem(semid, 0, 1);
pid_t id = fork();
int _semid = getsem(0);
if (id == 0)
{// child
while (1)
{
// p(_semid, 0);
printf("A");
fflush(stdout);
usleep(100000);
printf("A ");
fflush(stdout);
usleep(300000);
// v(_semid, 0);
}
}
else
{//father
while (1)
{
// p(_semid, 0);
printf("B");
fflush(stdout);
usleep(200000);
printf("B ");
fflush(stdout);
usleep(100000);
// v(_semid, 0);
}
wait(NULL);
}
destroysem(semid);
return 0;
}
測試結果:
通過結果截圖我們可以看出,在不加P、V操作之前,輸出的並不是我們想要的兩個相同的字符成對出現的情況。
此時,顯示器只有一個,兩個進程同時打印,此時顯示器成爲臨界資源,這就需要我們加互斥鎖來進行保護。互斥鎖就是二元信號量,
當使用互斥鎖進行保護後(即加上P、V操作之後),屏幕上就會成對成對的打印我們所想要的字符,而不會出現交叉打印的現象。
和其他進程間通信的方式一樣,信號量依然可以使用ipcs -s命令來查看,使用ipcrm -s+標號進行刪除。