版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/83239942
--------参考文献 W.Richard Stevens, Stephen A.Rago.UNIX环境高级编程[M].北京:人民邮电出版社,2014.6:455-459.
目录
一、问题引出
1.1 情景模拟
某进程向某个txt文件写入"hello",另一进程向该txt文件读出已经写入的"hello"并在屏幕上打印输出。
毋庸置疑,写完了才能读取,读取完了才能写入。这两个进程运行时就需要额外施加同步控制(边读边写会导致不可预料的后果)。在本情景下,该txt文件被看成是资源,而且只有一份。
生活中,比较贴切的例子就是打印店的打印机,假设打印机一共有5台,每台电脑到底使用哪台打印机是未知的,因为电脑对任何一台打印机都没有特殊要求。打印机就被看成是资源,而且有5份。
举这个例子是为了说明,资源不是总是只有1份的,而且申请资源或释放资源时不总是一份份地申请或释放。一切都按照实际需求去做。
1.2 设计方案
如图1所示,自主实现的函数用蓝色圆角矩形框起来,库函数用橘色椭圆框起来, 我们实现的,实际上更多的是业务处理,并非底层实现。自主实现的函数内部到最后都会去转调库函数(代理)。
1.3 相关知识
创建一个信号量集或者打开一个现有信号量集函数semget
#include<sys/sem.h>
int semget(key_t key, int nsems, int flag);
//返回值:若成功,返回信号量ID;若出错,返回-1。
- key用于变换标识符。
- nsems该集合中信号量数。如果是创建新集合(一般在服务器进程中),则必须指定nsems。如果是引用现有集合(一个客户进程),则将nsems指定为0。
信号控制函数semctl包含多种信号量的操作:
#include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);
//返回值实际含义是什么由参数cmd所指定,在这里,int不是一个冰冷的数字,而是有丰富含义的。
- semid是信号量集在内核中的编号;
- semnum是该集合中某个特定信号量的下标,取值∈[0, nsems-1];
- cmd是(幻数),代表10种命令的一种(不详细展开到底是哪10种命令);
- arg是选项参数,这个参数是一个联合体,而非指向联合体的指针。
无名结构:
struct
{
unsigned short semval; //资源总数,always >= 0
pid_t semid; //上一次操作该信号量的进程ID
unsigned short semncnt; //用于进程挂起的情形
unsigned short semzcnt;
}
联合体成员如下:
union semun{
int val; //val在参数cmd等于SETVAL时会用到,用于设置成员semnum的semval的值。
struct semid_ds *buf; //buf在参数cmd等于IPC_STAT或者IPC_SET时会用到。
unsigned short *array; //array在参数cmd等于GETALL或者SETALL时会用到。
};
联合体semun中出现的semid_ds结构体:
struct semid_ds{
struct ipc_perm sem_perm; //权限控制。
unsigned short sem_nsems; //集合中信号量的数目。
time_t sem_otime; //最后一次调用semop()函数的时间。
time_t sem_ctime; //最后一次改变本结构体成员值的时间。
.
.
.
};
自动执行信号量集合上的操作数组semop
#include<sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
//返回值:若成功,返回0;若出错,返回-1。
- semid是信号量在内核中的编号;
- semoparray是一个指针(指向一个数组,数组元素类型是struct sembuf);
- nops是被指针指向的数组元素的个数。
struct sembuf{
unsigned short sem_num; //信号量在信号量集合中的下标,取值∈[0,nsems-1]
short semop; //操作(正整数positive、0、负整数negative)
short sem_flg; //命令IPC_NOWAIT、SEM_UNDO
}
二、 代码实现
2.1 头文件semaphore.h
#ifndef __SEM_H
#define __SEM_H
#include<sys/types.h>
#include<sys/sem.h> //semget() semctl semop()
#include<fcntl.h>
typedef union semun
{
int val; //资源数目
struct semid_ds *buf; //for IPC_STAT and IPC_SET
unsigned short *array;//for GETALL and SETALL
}semun;
int set_init(int key); //初始化信号量的函数
void set_p(int semid, int semnum); //申请资源
void set_v(int semid, int semnum); //释放资源
void sem_del(int semid); //删除信号量
#endif
2.2 源文件semaphore.c
#include"semaphore.h"
int sem_init(int key){
int semid = semget((key_t)key, 0, 0666);
if(-1 == semid)
{
semid = semget((key_t)key, 1, 0666|IPC_CREAT);
semun un;
un.val = 0; //setval
semctl(semid, 0, SETVAL, un); //un.val
}
return semid;
}
/*
* struct sembuf
* {
* unsigned short sem_num; //member # in set(0, 1, ..., nsems-1)
* short sem_op; //operation(negative, 0, or pasitive)
* short sem_flg; //IPC_NOWAIT, SEM_UNDO
* }
*
* */
void sem_p(int semid, int semnum){ //申请资源
struct sembuf buf;
buf.sem_num = semnum;
buf.sem_op = -1;
semop(semid, &buf, 1);
}
void sem_v(int semid, int semnum){ //释放资源
struct sembuf buf;
buf.sem_num = semnum;
buf.sem_op = 1;
semop(semid, &buf, 1);
}
void sem_del(int id){
semctl(id, 0, IPC_RMID);
}
2.3 测试文件a.c和b.c
//a.c
#include"semaphore.h"
#include<assert.h>
int main()
{
int semid = sem_init(1996);
int fd = open("a.txt", O_WRONLY | O_CREAT, 0664);
assert(-1 != fd);
sleep(2);
write(fd, "hello", 5);
sem_v(semid, 0);
close(fd);
return 0;
}
//b.c
#include<assert.h>
#include<stdio.h>
#include"semaphore.h"
int main()
{
int semid = sem_init(1996);
sem_p(semid, 0);
int fd = open("a.txt", O_RDONLY | O_CREAT, 0664);
assert(-1 != fd);
char buff[128] = {0};
read(fd, buff, 127);
printf("%s\n", buff);
close(fd);
sem_del(semid);
return 0;
}