- 信号量
信号量与之前的PIPE、FIFO以及消息队列不同。它是一个计数器,用于为多个进程提供对共享数据对象的访问。信号量在负责数据操作的互斥、同步等功能。
学习信号量之前首先先来学习几个基本概念:
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源,如“第一类读写者模型”。
临界资源:多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。
临界区:指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
- 为什么要使用信号量
有时我们会出现多个程序同时访问一个共享资源,而同时多道程序都要对共享资源进行操作,那么这样就会出问题。
例如:这里的共享资源据说显示器,父子进程向显示器发送字符A、B
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id=fork();
if(id==0)
//child
{
while(1)
{
usleep(10000);
printf("A");
fflush(stdout);
}
}
else if(id>0)//father
{
while(1)
{
usleep(10000);
printf("B");
fflush(stdout);
}
wait(NULL);
}
else
{
printf("fork is faild\n");
}
return 0;
}
可以看到打印出来的序列有AB也有AABB,信号量大致就是用来解决这种问题的。
对于这一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。共享内存的使用就要用到信号量。
- 信号量的工作原理:
为了获得共享资源,进程需要执行下列操作:
1)测试控制该资源的信号量。
2)若此信号量为正,则进程可以使用该资源。这种情况下,进程会将信号量值减1,表示使用了一个资源单位。
3)否则,若信号量为0,则进程进入休眠状态,直至信号量值大于0.进程呗唤醒后,返回步骤1。
为了正确实现信号量,信号量值的测试及减1应当是原子操作。为此信号量通常是在内核实现的。常用的信号量形式被称为二元信号量。它控制单个资源,其初始值为1。
- 信号量的特点
- 和消息队列一样。信号量是在内核中实现的,所以信号量声明周期是随内核的,当该进程结束信号量是没有被摧毁的。
- 信号量不是单个的非负值,而必须定义为含有一个或多个信号量值的集合,需要给定信号量集合中信号量值的个数。
- 信号量的创建是独立于它的初始化的,这是一个致命的缺点,因为不能原子的创建一个信号量集合,并对该集合中的各个信号量值就行赋值。
信号量的相关函数:
- ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
用来产生提供产生一个键,每一个IPC对象都与一个键相关联,这个键作为该对象的外部名。
2. semget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
返回值:若成功,返回信号量ID;若出错,返回-1;
将key变为标识符的规则,讨论了是创建一个新集合,还是引用一个现有集合。创建一个新集合时,要对semid_ds结构的成员赋初值。
nsems是该信号量集合中的信号量数。如果是创建新集合,则必须制定nsems。如果引用现有集合,则将nsems设置为0。
3. semctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
返回值:对于GETALL以外的所以GET命令,semctl函数都返回相应值。对于其他命令,若成功则返回0,出错,则设置errno并返回-1;
参数:
第四个参数是可选的,是否使用取决于所请求的命令,如果使用该参数,则类型是semun,它是多个命令特定参数的联合(union):
union semun{
int val; /*for SETVAL*/
struct semid_ds *buf; /for IPC_STAT and IPC_SET*/
unsigned short *array /*for GETALL and SETALL*/
}
注意,这个选项参数是一个联合,而不能是指向联合的指针。
cmd参数是选择命令,根据操作目的选择命令。
cmd命令 | 作用 |
---|---|
SETVAL | 设置semnum的semval值 |
IPC_RMID | 从系统中删除该信号量集合 |
4. semop
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信号量ID。
sops是一个指针它指向一个有sembuf结构表示的信号量操作数组。如下:
struct sembuf{
unsigned short sem_num; /*表示进行操作的信号集合中的信号量下标*/
short sem_op; /*表示操作方式*/
short sem_flg; /*IPC_NOWAIT,SEM_UNDO*/
}
npos表示信号量集合当中信号量的个数。
sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
信号量实例:
/**********************************************************************
> File Name: sem.c
> Author:
> Mail:
> Created Time: Mon 12 Jun 2017 01:00:25 AM PDT ************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#define KEY_PATH "."
#define PROJECT_ID 88
/*struct sembuf{
unsigned short sem_num;
short sem_op;
short sem_flg;
};*/
static int comm_sem(int flag,int nsem)
{
key_t semkey=ftok(KEY_PATH,PROJECT_ID);
if(semkey<0)
{
printf("ftok is faild\n");
}
return semget(semkey,nsem,flag);
}
int create_sem(int _semset_num)
{
return comm_sem(IPC_CREAT|0666,_semset_num);
}
int init_sem(int _sem_id,int _which)
{
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};
union semun sem_un;
sem_un.val=1;
return semctl(_sem_id,_which,SETVAL,sem_un);
}
int get_sem()
{
return comm_sem(IPC_CREAT,0);
}
static int op_sem(int sem_id,int which,int op)
{
struct sembuf sem_buf;
memset(&sem_buf,0,sizeof(sem_buf));
sem_buf.sem_num=which;
sem_buf.sem_op=op;
sem_buf.sem_flg=0;
return semop(sem_id,&sem_buf,1);
}
int sem_p(int sem_id,int which)
{
return op_sem(sem_id,which,-1);
}
int sem_v(int sem_id,int which)
{
return op_sem(sem_id,which,1);
}
int destory_sem(int _sem_id)
{
return semctl(_sem_id,0,IPC_RMID);
}
int main()
{
int sem_id=create_sem(1);
printf("semid:%d",sem_id);
init_sem(sem_id,0);
pid_t id=fork();
if(id==0)
{
//child
while(1)
{
sem_p(sem_id,0);
usleep(10000)
printf("A");
fflush(stdout);
usleep(2000);
printf("A");
fflush(stdout);
sem_v(sem_id,0);
}
}
else if(id>0)//father
{
while(1)
{
sem_p(sem_id,0);
usleep(10000);
printf("B");
fflush(stdout);
usleep(2000);
printf("B");
fflush(stdout);
sem_v(sem_id,0);
}
wait(NULL);
}
else
{
printf("fork is faild\n");
}
destory_sem(sem_id);
return 0;
}
加入信号量之后发现,AABB成对出现,所以达到了互斥的目的。