目录
- 信号量的概念及特点
- linux 信号量的相关函数
- 案例
(一)信号量的概念及特点
上次分析的信号和这次信号量长得像,但不是一个东西。
- 信号:相当于软中断
- 信号量:调协进程对共享资源的访问,让一个临界区同一时间只有一个线程在访问它,为什么要引入信号量呢?举个例子,有两个进程要使用打印机,但是没有保证在同一时刻只有一个进程使用打印机,那打印的东西就会是乱的了。信号量在进程的同步与互斥有很重要的应用。
(二) linux 信号量的相关函数
(1)新建信号量
有些思路像消息队列,可以一起学习,类比起来,下面是消息队列的文章:
https://blog.csdn.net/weixin_39956356/article/details/86652957
//在程序的开头,就要使用这个函数,就像open一样返回的句柄,供semop,semctl使用
int semget(key_t key,int nsems,int semflg)
参数分析:
- key:信号量关联的标识符,本质是整数,可以弄个整数强制转化下就可以了,如(key_t)1234
- nsems:需要的信号量数目,它的值几乎总是1
- msgflg:权限+创建(如果不存在则创建,存在则打开)
IPC_CREAT:如果内核中没有此队列则创建它
当IPC_EXCL和IPC_CREAT一起使用时,如果队列已经存在,则失败。
注:我们经常使用的两个组合:
IPC_CREAT|IPC_EXCL|0666 和 IPC_CREAT|0666
- IPC_CREAT|IPC_EXCL|0666:如果没有这个信号则创建一个, 如果已经存在则产生一个存在错误
- IPC_CREAT|0666:没有创建信号,有则返回已经存在的信号 ID
(2)信号量控制函数
int semctl(int semid,int semnum,int cmd, /*union semun arg*/)
- semid:semget返回的id
- semnum:信号量集中的第 semnum 个信号量。它的取值范围:0~nsems-1 。(The semaphores in a set are numbered starting at 0.),从0开始的
- cmd:要操作的具体命令
以下是两个最常见的命令:
SETVAL:用于初始化信号量
IPC_RMID:用于删除信号量
如:初始化这么使用
注:编号从0~nsems-1的。
//初始化信号量--之前设置了一个信号量,所以从0开始初始化,而设置值要通过联合赋值
void cre_semaphore()
{
union semun sem_union;
sem_union.val = 1; //初始化信号量
if(semctl(semid, 0, SETVAL, sem_union) == -1)
{
fprintf(stderr, "Initializing semaphore error!!!\n");
exit(EXIT_FAILURE);
}
}
如:删除这么使用
//删除信号量-只有一个
void del_semaphore()
{
union semun sem_union;
if(semctl(semid, 0, IPC_RMID, sem_union) == -1)
{
fprintf(stderr, "Failed to delete semaphore!!!\n");
exit(EXIT_FAILURE);
}
}
这里涉及一个联合–要自己定义要自己定义:
//头文件在/usr/include/bits/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) */
};
- 第一个设置值的,第二个IPC_STAT、IPC_SET 使用的时候才用到,第三个GETALL、SETALL 使用的时候才用到
(3)p,v操作
semop可以实现p,v操作,还涉及一个很重要的结构体struct sembuf
int semop(int semid, struct sembuf *sops, unsigned nsops);
先讲结构体struct sembuf:
//源文件/usr/include/sys/sem.h
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
分析:
- sem_num:除非使用一组信号量,否则它为0,这里就一个,所以为0
- sem_op:操作时需要改变的数据, -1即P等待, +1即V发送信号
- sem_flg:有两个值:IPC_NOWAIT和SEM_UNDO
通常为SEM_UNDO,使操作系统跟踪信号,万一用户忘记处理,操作系统会自动跟踪解决
之后是semop参数分析:
- semid:信号集的识别码
- sops:指向struct sembuf指针
- nsops:信号操作结构的数量,恒大于或等于1
- 返回值:If successful semop() return 0; otherwise they return -1 with errno
P操作:-在man文档里面有个用法,其实差不多
//对信号量做减1操作,即等待P(sv)
int semaphore_p()
{
struct sembuf sem_b; //先定义个struct sembuf
//struct sembuf赋值
sem_b.sem_num = 0;
sem_b.sem_op = -1; //p操作-1
sem_b.sem_flg = SEM_UNDO; //系统跟踪
if(semop(semid, &sem_b, 1) == -1)
{
fprintf(stderr, "Semaphore_p error!!!\n");
return 1; //出错
}
return 0;
}
V操作:
//对信号量做加1操作,即V(sv)
int semaphore_v()
{
struct sembuf sem_b; //先定义个struct sembuf
//struct sembuf赋值
sem_b.sem_num = 0;
sem_b.sem_op = 1; //V操作+1
sem_b.sem_flg = SEM_UNDO; //系统跟踪
if(semop(semid, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v error!!!\n");
return 1; //出错
}
return 0;
}
(三)案例
很简单的想法:就是两个进程一个带X,一个带a,谁先抢到临界区,谁就进去,在进去的时候打印一下,出来的时候打印下。即都是两个两个的,没有交替的!
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
/****************************************************************************************
** 进程互斥
** semget():创建信号量--类比于open
** int semget(key_t key, int nsems, int semflg);
** key:
** 信号量关联的标识符
** semflg:信号量的建立标志和存取权限
** IPC_CREAT:如果内核中没有此信号量则创建它
** 当IPC_EXCL和IPC_CREAT一起使用时,如果信号量已经存在,则失败
** RETURN VALUE
** 执行成功则返回信号量的标识符(a nonnegative integer-非负整数),否则返回-1
** 注意:
** key_t本质是整数,你自己弄个整数强制转化下就可以了,如(key_t)1234
** semop():PV操作
** int semop(int semid, struct sembuf *sops, size_t nsops);
** semid : 信号量的标识码(semget返回的id)
** *sops : 系统已经定义好了,自己赋值就可以了,和msgsnd不同哦,要自己定义。
** nsops : 信号操作结构的数量,恒大于或等于1
** RETURN VALUE
** On success, zero is returned. On error, -1 is returned
** semctl():信号量的控制函数
** int semctl(int semid,int semnum,int cmd, union semun arg(可选));
** semid : 信号量的标识码(semget返回的id)
** cmd : SETVAL:用于初始化信号量
** IPC_RMID:用于删除信号量
** union semun : 这个联合和cmd相关,要自己定义
** RETURN VALUE
** 成功返回数据长度,错误返回-1
** fflush():清除读写缓冲区,需要立即把输出缓冲区的数据进行物理写入时
** int fflush(FILE *stream);
** stream: 几种标准的流/文件句柄
** RETURN VALUE
** Upon successful completion 0 is returned. Otherwise, EOF is returned and errno
****************************************************************************************/
union semun
{
int val; //设置信号量的初始值
struct semid_ds *buf; //Buffer for IPC_STAT, IPC_SET
unsigned short *array; //Array for GETALL, SETALL
};
static int semid = 0; //全局变量,供所有函数使用
static int cre_semaphore(); //初始化信号量
static void del_semaphore(); //删除信号量
static int semaphore_p(); //等待--semaphore_p---1
static int semaphore_v(); //发送信号--semaphore_v--1
int main(int argc, char *argv[])
{
char message = 'X';
int i = 0;
semid = semget((key_t)1234, 1, 0666|IPC_CREAT); //创建信号量
if(argc>1)
{
//程序第一次被调用,初始化信号量
if(!cre_semaphore()){
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//设置参数的第一个参数第一个,message是char,所以要一个就可以了,比如你输入./seml a---a就是message了
message = argv[1][0];
sleep(2);
}
for(i = 0; i < 10; i++)
{
//进入临界区
if(semaphore_p())
exit(EXIT_FAILURE);
printf("%c",message);
//清除缓冲区,然后随机休眠
fflush(stdout);
sleep(rand()%3);
//离开临界区前再一次向屏幕输出数据
printf("%c",message);
fflush(stdout);
if(semaphore_v())
exit(EXIT_FAILURE);
sleep(rand()%2);
}
sleep(10);
printf("\n%d---finished!!!\n",getpid());
//如果程序是第一次被调用,则在退出前删除信号量
if(argc > 1)
{
sleep(3);
del_semaphore();
}
exit(EXIT_SUCCESS);
}
//初始化信号量--之前设置了一个信号量,所以从0开始初始化,而设置值要通过联合赋值
static int cre_semaphore()
{
union semun sem_union;
sem_union.val = 1; //初始化信号量
if(semctl(semid, 0, SETVAL, sem_union) == -1)
return 0;
return 1;
}
//删除信号量-只有一个
static void del_semaphore()
{
union semun sem_union; //先定义个struct sembuf
if(semctl(semid, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore!!!\n");
}
//对信号量做减1操作,即等待P(sv)
static int semaphore_p()
{
struct sembuf sem_b; //先定义个struct sembuf
//struct sembuf赋值
sem_b.sem_num = 0;
sem_b.sem_op = -1; //p操作-1
sem_b.sem_flg = SEM_UNDO; //系统跟踪
if(semop(semid, &sem_b, 1) == -1)
{
fprintf(stderr, "Semaphore_p error!!!\n");
return 1; //出错
}
return 0;
}
//对信号量做加1操作,即V(sv)
static int semaphore_v()
{
struct sembuf sem_b; //先定义个struct sembuf
//struct sembuf赋值
sem_b.sem_num = 0;
sem_b.sem_op = 1; //V操作+1
sem_b.sem_flg = SEM_UNDO; //系统跟踪
if(semop(semid, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v error!!!\n");
return 1; //出错
}
return 0;
}
输出: