虽然本文是记录使用信号量保证进程的同步与互斥的,但是其实也可以看做是进程之间的通信问题,为了与前面的保持一致,所以还是叫做 Linux进程间通信了!
进程间通信的方式有管道、消息队列、共享内存这些都是进程间的信息通信,而信号量可以理解为进程使用的临界资源的状态说明,信号量主要用于保证同步与互斥
由此可见我们之前用于进程间通信的管道,消息队列,共享内存都是临界资源,管道是内核已经提供了同步与互斥,但是消息队列和共享内存都是不保证同步与互斥的
信号量:本质上是一把计数器 如果一个信号只有0或者1,那么这个就是二元信号量,所以二元信号量可以实现互斥锁
P操作:计数器 --
V操作:计数器 ++
信号量本身也是临界资源,所以P、V操作必须是原子的
cstruct ipc_perm {
key_t __key; // 提供给 semget()的键
uid_t uid; // 所有者有效 UID
gid_t gid; // 所有者有效 GID
uid_t cuid; // 创建者有效 UID
gid_t cgid; // 创建者有效 GID
unsigned short mode; // 权限
unsigned short __seq; // 序列号
};
//信号量集的结构
struct semid_ds {
struct ipc_perm sem_perm; // 所有者和权限
time_t sem_otime; // 上次执行semop的时间
time_t sem_ctime; // 上次更新时间
unsigned short sem_nsems; // 在信号量集合里的索引
}
作用:用于创建信号量集
c#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key
信号集的名字,这个与再创建管道、消息队列、共享内存等用的key是一致的
nsems
信号集中信号量的个数,一般为1(信号集底层就是数组)
semflg
同创建消息队列等一样的权限,sem_flags取两个值,IPC_CREATE
和 IPC_EXCL
,需要配权限使用
return
成功返回一个非负整数,即该信号集的标识码;失败返回-1
num_sems:信号量的数目,
作用:用于控制信号量集
c#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int semnum, int cmd, ...);
shmid
这个就是要控制的信号量集
semnum
这个是具体要控制的信号量,因为shmid只能指明是哪一个信号量集(数组),而semnum就是数组下标
cmd
将要采取的动作(有三个可取值)
作用:修改信号量集中的值
c#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid
这个就是要修改的信号量集
sops
如下结构体的指针,这个结构体是这样的:
cstruct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};
nsops
信号量的个数
return
成功返回0,失败返回1
makefile
makefiletest:comm.c main.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf $@
comm.c
&& comm.h
&& main.c
c#ifndef __COMM_H__
#define __COMM_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <wait.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
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 */
};
int createSemSet(int nums);
int initSem(int semid, int nums, int initVal);
int getSemSet(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destorySemSet(int semid);
#endif //!__COMM_H__
//---------------------comm.c----------------------------
#include "comm.h"
static int commSemSet(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 createSemSet(int nums){
return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}
int getSemSet(int nums){
return commSemSet(nums, IPC_CREAT);
}
int initSem(int semid, int nums, int initVal){
union semun _un;
_un.val = initVal;
if(semctl(semid, nums, SETVAL, _un) < 0){
perror("semctl");
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){
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 destorySemSet(int semid){
int ret = semctl(semid, 0, IPC_RMID);
if(ret < 0){
perror("semctl");
return -1;
}
return ret;
}
//----------------------------main.c----------------------
#include "comm.h"
int main(){
int semid = createSemSet(1);
initSem(semid, 0, 1);
pid_t id = fork();
if(id == 0){
int _semid = getSemSet(0);
while(1){
//P(_semid, 0);
printf("A");
fflush(stdout);
usleep(100000);
printf("A");
fflush(stdout);
usleep(100000);
//V(_semid, 0);
}
}
else{
while(1){
//P(semid, 0);
printf("B");
fflush(stdout);
usleep(100000);
printf("B");
fflush(stdout);
usleep(100000);
//V(semid, 0);
}
wait(NULL);
}
destorySemSet(semid);
return 0;
}
打开PV操作时与未打开时的对比:
同样的使用
ipcs -s
命令即可查看信号量,使用ipcrm -s
即可释放信号量资源
本文作者:Tim
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!