信号量
信号量,我们可以理解为一种计数器,记录的是可共享资源的数量。信号量主要被用于协调多个进程对同一共享资源的占用访问。
如果一个进程想要获得并使用共享资源,需经历以下过程:
- 测试查看控制该共享资源的信号量。
- 若该信号量的值大于0,则进程可以使用该资源。资源分配给该进程以后,信号量的值减1。表示该资源分配出去到了一个单位。
- 若该信号量的值等于0,则该进程将被挂起,进入休眠等待状态。直到资源被释放,信号量的值大于0,该进程返回步骤1。(进程被挂起:把控制进程的数据结构PCB链接到信号量集的等待队列中)
- 当一个共享资源被释放时,控制该资源的信号量将加1。
上述的共享资源我们通常称为临界资源,进程中访问临界资源的代码称为临界区。
现在我们可知,信号量是为了保护临界资源而被设立的(防止多个进程抢占同一临界资源),而信号量本身也算是一种临界资源。信号量又由谁来保护呢?为了解决这一问题,信号量的加1减1操作,均被设计成原子操作来保护自身。(原子操作:要么不做,要么做完做成功)
存在形式
信号量同我们人类一样,是“群居动物”。它并非是单一的存在。而必须定义为一个含有一个或多个信号量的集合,信号量集。当我们创建这个信号量集的时候,需要指定信号量的个数。
信号量集有一个最大缺陷就是,信号量集的创建(semget)与信号量值得初始化(semctl)是相互独立开的。不是原子操作。这样很危险。例如,当我们创建好了一个信号量集,在我们还没有对它进行初始化时,该信号量集就被分配使用,造成无法预料的后果。
同其他PC一样,当占用共享资源的所有进程都执行结束,信号量并不会被程序主动释放掉,所以这种程序总是存在潜在的危险性。
信号量的维护
操作系统内核为每个信号量集合都维护着一个semid_ds结构:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集中的每个信号量对应其中一个数组元素 */
ushort sem_nsems; /* sem_base 数组的个数 */
time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */
time_t sem_ctime; /* 成功创建时间 */
};
每个信号量又有一个无名结构表示:
struct sem {
ushort semval; /* 信号量的当前值 */
short sempid; /* 最后一次返回该信号量的进程ID 号 */
ushort semncnt; /* 等待semval大于当前值的进程个数 */
ushort semzcnt; /* 等待semval变成0的进程个数 */
};
信号量的操作函数
semget 获取信号量
定义为:
int semget(key_t key,int nsems,int semflg)
参数:
- key,为key_t类型的值。System V IPC使用此类型作为系统对它们的唯一标识(名字)。实就为int类型。通常使用ftok函数类获取key值。ftok把一个已经存在的路径名和一个整数标识转换成一个key_t值。
定义:
key_t ftok(const char* pathment,int proj_id);
- nsems为信号量的个数。
- semflg为创建的方式。选项有IPC_CREAT和IPC_EXCL。有以下两种使用方式:
~ IPC_CREAT和IPC_EXCL一起使用。表示创建一个信号量集,若所需的信号量集已存在,则报错退出。
~ IPC_CREAT单独使用。表示创建一个信号量集,若所需的信号量集已存在,则直接获取。
semop,对信号量进行加减操作
定义为:
int semop(int semid,struct s