信号量(semaphore)是一种用于提供不同进程之间或者一个给定的不同线程间同步手段的原语。信号量多用于进程间的同步与互斥,简单的说一下同步和互斥的意思:
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的先后执行顺序。
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。
共享资源通常分为两类:一类是互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。
信号量通信机制主要用来实现进程间同步,避免并发访问共享资源。信号量可以标识系统可用资源的个数。最简单的信号量为二元信号量。
当有进程要求使用共享资源时,需要执行以下操作:
1.系统首先要检测该资源的信号量;
2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
下图为 Linux 信号量通信机制的概念图。在实际应用中,两个进程通信可能会使用多个信号量,因此,Linux 在管理时以信号量集合的概念来管理。
具体代码如下,可以直接编译测试:
sem_comm.c
#include"sem_comm.h"
int sem_P(int smd_id, int sn)
{
struct sembuf op;
//指定第sn个信号量
op.sem_num=sn;
//当调用sem_P, 信号量结构struct sem中字段 semval
//减去sem_op值即可,即 semval-1
op.sem_op=-1;
//当sem_flg赋值为SEM_UNDO时,如果执行sem_P
//操作后,某个子进程退出, 此时其他进程调用get_val(sem_id)的时候,
//返回值得sem 字段 semval保持不变.
op.sem_flg = 0;//SEM_UNDO
semop(smd_id, &op, 1);
return 0;
}
int sem_V(int smd_id, int sn)
{
struct sembuf op;
//指定第sn个信号量
op.sem_num=sn;
//当调用sem_V, 信号量结构struct sem中字段 semval
//减去sem_op值即可,即 semval+1
op.sem_op=1;
//当sem_flg赋值为SEM_UNDO时,如果执行sem_V
//操作后,某个子进程退出, 此时其他进程调用get_val(sem_id)的时候,
//返回值得sem 字段 semval保持不变.
op.sem_flg = 0; //SEM_UNDO
semop(smd_id, &op, 1);
return 0;
}
int sem_create(key_t sem_key)
{
int sem_id;
//创建sem_id
sem_id = semget(sem_key, 1, IPC_CREAT|IPC_EXCL|0666);
if (sem_id == -1)
{
if (errno != EEXIST)
{
//printf("semget failed!!\n");
//return -1;
ERR_EXIT("semget failed!!");
}
printf("There is a semaphore in the system\n");
//返回一个存在的sem id
sem_id = semget(sem_key,1,0666);
if (-1 == sem_id)
{
printf("semget error\n");
return -1;
}
}
printf("sem_id=%x\n", sem_id);
return sem_id;
}
//sn代表信号的序号,第一个从0开始.
int sem_init(int sem_id, int val , int sn)
{
union semun arg;
//初始化信号量值,一般情况下为1.
//执行sem_P , 信号量当前值semval为0 , 其他进程
//无法访问,只能等待, 当执行sem_V,信号量当前值semval为1,其他进程可以继续
//使用该信号量
arg.val = val;
semctl(sem_id, sn, SETVAL, arg);
return 0;
}
//指定获取第几个信号值, 默认sn一般为0
int sem_getval(int semid, int sn)
{
int ret = semctl(semid, sn, GETVAL, 0);
if (ret == -1)
ERR_EXIT("semctl");
printf("current val is %d\n", ret);
return ret;
}
//删除sem_id 信号量
int sem_del(int sem_id)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
printf("sem delete\n");
exit(1);
}
return 0;
}
sem_comm.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/ipc.h>
//本联合未在出现在任何系统头文件中,因此必须由应用程序声明
union semun{
int val;// cmd == SETVAL ,此时 semval = arg.val
struct semid_ds *buf;
unsigned short *array;
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int sem_create(key_t sem_key);
int sem_init(int sem_id, int val , int sn);
int sem_getval(int semid, int sn);
int sem_P(int smd_id, int sn);
int sem_V(int smd_id, int sn);
int sem_del(int sem_id);
sem_test.c
#include"sem_comm.h"
int main(int argc, char const *argv[])
{
pid_t pid;
int sem_id;
key_t sem_key;
sem_key = ftok("xxxxx", 'A');
printf("sem_key:%x\n", sem_key);
sem_id = sem_create(sem_key);
sem_init(sem_id, 1, 0);
if ((pid = fork())<0){
printf("fork error\n");
exit(1);
}else if (pid == 0){
sem_P(sem_id,0);//p操作
printf("child running...\n");
sleep(2);
printf("child %d, returned value:%d\n", getpid(), pid);
sem_V(sem_id, 0);//v操作
}else{
sem_P(sem_id,0);//p操作
printf("Parent running\n");
sleep(2);
printf("parent %d, returned value :%d\n", getpid(), pid);
sem_V(sem_id,0);// v操作
//等待子进程退出,并做清除动作
waitpid(pid,0,0);
sem_del(sem_id);
exit(0);
}
return 0;
}
makefile
.PHONY: all clean
all:sem_test
sem_test:sem_test.o sem_comm.o
gcc -o $@ $^
%.o:%.c
gcc -c $<
clean:
rm -rf sem_test *.o