江苏省建设执业资格中心网站网络怎样做推广
前面学习了pipe,fifo,共享内存,信号。
本章将讲述信号量。
一、什么是信号量/信号量集?
1.什么是信号量
信号量是一个计数器。信号量用于实现进程间的同步和互斥。而可以取多个正整数的信号量被称为通用信号量。
对信号量的使用场景的解读
房间:临界资源(操作系统中的多个进程,他们共享各种资源,然而很多资源1次只能供1个进程使用。这种1次仅允许1个进程使用的资源称为临界资源。)
钥匙:信号量
只有拿到钥匙才能进入房间(如果进程A正在访问临界资源,则不允许进程B访问临界资源)。
2.什么是信号量集
linux中的信号量不止一个,有很多个。
二、P操作和V操作
p操作:取钥匙
V操作:放回钥匙
三、相关API
1.semget-----创建信号量
通过semget 来创建信号量或获取一个已有的信号量,
函数原型:#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);参数:key 信号量的关键字,可以通过ftok() 创建,详细看 进程间通信——消息队列nsems 信号量的数目(比如1代表信号量集合中有1个信号量)。如果是创建新的信号量,必须要指定nsems。如果是引用现有的信号量,nsems指定为0。shmflg 有两个选项,IPC_CREAT 表示内核中没有此信号量则创建它。IPC_EXCL 当和IPC_CREAT一起使用时,如果信号量已经存在,则返回错误。返回值:
成功返回标识符,否则返回-1。通过errno和perror函数可以查看错误信息。注意:
通过nsems 可以看出,创建一个信号量,里面可能包含多个信号量值。该信号量就相当于一个集合。该值表明有多少个共享资源单位可供共享应用。下面semctl 也是通过nsems 中的某一个进行操作。
如果nsems 设为1,该信号量又被称为二元信号量(binary semaphore)
2.semctl------初始化信号量
这个函数可以有3个参数或者4个参数
#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);@semid:信号量数组标识@semnum:要操作的信号量数组的信号量下标(比如:0代表操作第0个信号量)(该下表和数组下标一致,比如有1个信号量,则下表为0,有2个信号量则下表为1)@cmd:IPC_STAT 查询此信号量数组的数据存入arg.buf(buf为struct semid_ds结构体指针)IPC_RMID 删除指定semid的信号量数组GETVAL 获取信号量的当前值,SETVAL 设置信号量的值,初始化要用的命令
————————————————
版权声明:本文为CSDN博主「abist」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/abist/article/details/117606809This function has three or four arguments, depending on cmd. Whenthere are four, the fourth has the type union semun. The calling pro‐gram must define this union as follows:当使用4个参数时,要定义下面的联合体union semun {int val; /* 钥匙的数量 */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};
3.semop---信号量的操作
int semop(int semid,struct sembuf *sops,size_t nsops);函数的参数
参数semid 为信号量集的标识符;
参数 sops 指向进行操作的结构体数组的首地址;
参数 nsops 指出将要进行操作的信号的个数。返回值
semop 函数调用成功返回 0,失败返回 -1。sembuf 结构体对应一个特定信号的操作。
该结构定义在 linux/sem.h,如下所示:
struct sembuf{unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号 short sem_op; //操作类型short sem_flg; //操作标志
};
sem_num标识信号量集中的第几个信号量,0表示第1个,1表示第2个,nsems - 1表示最后一个。sem_op标识对信号量的所进行的操作类型。对信号量的操作有三种类型:sem_op > 0,对该信号量执行挂出操作,挂出的值由sem_op决定,系统会把sem_op的值加到该信号量的当前值semval(参考文章开头关于每个信号量结构的定义)上。如果sem_flag指定了SEM_UNDO(还原)标志,那么相应信号量的semadj值会减掉sem_op的值。下面会说明semadj的含义。sem_op < 0,对该信号量执行等待操作,当信号量的当前值semval >= -sem_op时,semval减掉sem_op的绝对值,为该线程分配对应数目的资源。如果指定SEM_UNDO,相应信号量的semadj就加上sem_op的绝对值。 当semval < -sem_op时,相应信号量的semncnt就加1,调用线程被阻塞,直到semval >= -sem_op,当此条件满足时,调用线程被唤醒,执行相应的分配操作,然后semncnt减去1。sem_op = 0,表示调用者希望semval变为0。如果为0则立即返回,如果不为0,相应信号量的semzcnt加1,调用调用线程被阻塞。sem_flag:信号量操作的属性标志,如果为0,表示正常操作,如果为IPC_WAIT,使对信号量的操作时非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。下面解释一下与单个信号量相关的几个值:
semval:信号量的当前值,在文章开头信号量的结构中已提到。
semncnt:等待semval变为大于当前值的线程数。在文章开头信号量的结构中已提到。
semzcnt:等待semval变为0的线程数。在文章开头信号量的结构中已提到。
semadj:指定信号量针对某个特定进程的调整值。只有sembuf结构的sem_flag指定为SEM_UNDO后,semadj才会随着sem_op而更新。讲简单一点:对某个进程,在指定SEM_UNDO后,对信号量semval值的修改都会反应到semadj上,当该进程终止的时候,内核会根据semadj的值,重新恢复信号量之前的值。
4.P操作(取钥匙)
自定义
要利用semop()来实现void PGetKey(int id)//id是semget()的返回值
{struct sembuf set;//该结构定义在 linux/sem.hset.sem_num = 0;//这是下标,0代表第1个信号;set.sem_op = -1;//是指对钥匙数量减1;set.sem_flag = SEM_UNDO;//配置为SEM_UNDO当进程终止时,自动取消对该钥匙的操作semop(id,&set,1);
}
5.V操作(放回钥匙)
自定义
要利用semop()来实现void VputbackKey(int id)//id是semget()的返回值
{struct sembuf set;//该结构定义在 linux/sem.hset.sem_num = 0;//这是下标,0代表第1个信号;set.sem_op = 1;//是指对钥匙数量加1;set.sem_flag = SEM_UNDO;//配置为SEM_UNDO当进程终止时,自动取消对该钥匙的操作semop(id,&set,1);
}
四、实验
实验要求:
最初状态:没有钥匙。
创建子进程后,
让父进程取钥匙(由于钥匙的数量为0,所以父进程取钥匙的动作被阻塞),使用结束(访问临界资源)后打印“father process”,放回钥匙。
让子进程放钥匙,然后打印“child process”。
(由于最初父进程会被阻塞,所以程序运行结果是先打印"child process"后打印“father process")
思路分析
(1)生成键值
(2)创建信号量
(3)初始化信号量:定义联合体-->钥匙的个数置为0--->使用semctl初始化信号量
(4)创建子进程
(5)判断父子进程
--->在父进程里:取钥匙,打印“father process”,放回钥匙。
---->在子进程里:放钥匙,打印“child process"
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>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(Linux-specific) */};void VputbackKey(int id)//id是semget()的返回值
{struct sembuf set;//该结构定义在 linux/sem.hset.sem_num = 0;//这是下标,0代表第1个信号;set.sem_op = 1;//是指对钥匙数量加1;set.sem_flg = SEM_UNDO;//配置为SEM_UNDO当进程终止时,自动取消对该钥匙的操作semop(id,&set,1);printf("put back key\n");
}void PGetKey(int id)//id是semget()的返回值
{struct sembuf set;//该结构定义在 linux/sem.hset.sem_num = 0;//这是下标,0代表第1个信号;set.sem_op = -1;//是指对钥匙数量减1;set.sem_flg = SEM_UNDO;//配置为SEM_UNDO当进程终止时,自动取消对该钥匙的操作semop(id,&set,1);printf("get key\n");
}int main()
{key_t key;int semid;key = ftok(".",2);semid = semget(key,1,IPC_CREAT|0666);union semun initsem;initsem.val = 0;semctl(semid,0,SETVAL,initsem);int pid = fork();if(pid > 0){PGetKey(semid);printf("father process\n");VputbackKey(semid);}else if(pid == 0){VputbackKey(semid);printf("child process\n");}else{printf("fork error\n");}return 0;
}
五、如何销毁钥匙
semctl(semid,0,IPCIPC_RMID);//3个参数