今天我们来分享一下进程通行(IPC)-实现信号量。
信号量的本质是计数器,且进程与进程之间都能够看到,所以我们用一个信号量来衡量临界资源,通过计数器限制进程对临界资源进行操作的个数。也就是说信号量是用来保护临界资源的,并且因为信号量是原子的,所以在他能在保护自己的基础下去保护临界资源。
本文我们简单的用父子进程来展示二元信号量的作用,本文说用的二元信号量(初始为1,占用为0)可以起到互斥功能(相当于互斥锁),来保证同一时刻进入临界资源的进程只有一个,避免父子进程(多个进程)同一时间操作临界资源而导致无法保证实现不同进程之间的同步与互斥。
用父子进程举例说明同步与互斥被破坏的情况如下:
信号量需要注意的两点:
1》任意进程占用资源(信号量)时,因某些原因(时间片用尽)被暂时剥离,此时进程拥有信号量(锁),即会影响其他进程去申请资源(信号量),此时其他进程被迫等待。
2》任意进程占用资源(信号量)时,被用户或其他KILL时,由于被KILL时进程拥有信号量(锁),即会影响其他进程去申请资源(信号量),此时会造成死锁。
信号量缺点:
初始化和创建是否分开的,用户容易误操作。
由1》,可以看出信号量的另一个缺点,拥有信号量所在的多进程程序所用时间相对于没有信号量约束的同一进程会多一些。
信号量是随内核的,除了需要调用系统接口去建立信号量,还需要调用系统接口去销毁信号量。
最后,介绍关于查看信号量和销毁信号量的系统命令,截图如下:
实现信号量的父子进程代码如下:
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
union semu{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* _buf;
};
#define PATHNAME "."
#define PROJ_ID 0x6666
int CommSem(int flags);
int GreatSem();
int GetSem();
int InitSem(int semid, int nums, int val);
int PVcom(int semid, int which, int _op);
int P(int semid, int which);
int V(int semid, int which);
int DestroySem(int semid);
#endif
comm.c
#include"comm.h"
int CommSem(int flags){
int keys = ftok(PATHNAME, PROJ_ID);
if(keys<0){
perror("ftok");
return -1;
}
int semid = semget(keys, 1, flags);
if(semid<0){
perror("semget");
return -2;
}
return semid;
}
int GreatSem(){
return CommSem(IPC_CREAT|IPC_EXCL);
}
int GetSem(){
return CommSem(IPC_CREAT);
}
int InitSem(int semid, int nums, int val){
union semu signal;
signal.val = val;
if(semctl(semid, nums, SETVAL, signal)<0){
perror("semctl");
return -4;
}
return 0;
}
int PVcom(int semid, int which, int _op){
struct sembuf _buf;
_buf.sem_num = which;
_buf.sem_op = _op;
_buf.sem_flg = 0;
if(semop(semid, &_buf, 1)<0){
perror("semop");
return -5;
}
}
int P(int semid, int which){
return PVcom(semid, which, -1);
}
int V(int semid, int which){
return PVcom(semid, which, 1);
}
int DestroySem(int semid){
if(semctl(semid, 0, IPC_RMID)<0){
perror("semctl");
return -3;
}
return 0;
}
client.c
#include"comm.h"
#include<unistd.h>
#include<sys/wait.h>
int main(){
int semid = GreatSem();
printf("new semid:%d\n", semid);
InitSem(semid, 0, 1);
pid_t id = fork();
if(id<0){
perror("fork");
return -1;
}
else if(id == 0){//child
int _semid = GetSem();
int i=0;
while(i<10){
P(_semid, 0);
i++;
printf("A");
fflush(stdout);
sleep(1);
printf("A");
fflush(stdout);
sleep(1);
V(_semid, 0);
}
}
else
{
int i=0;;
while(i<10){
P(semid, 0);
i++;
printf("B");
fflush(stdout);
sleep(1);
printf("B");
fflush(stdout);
sleep(1);
V(semid, 0);
}
pid_t ret = waitpid(id, NULL, 0);
if(ret>0){
printf("wait success!\n");
}
}
DestroySem(semid);
return 0;
}
Makefile
client:client.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client
运行界面
分享如上!愿共同进步。