Linux进程间通信之信号量

whhq520 2012-08-09 11:11:13
作者:武汉华嵌 嵌入式培训 技术部



信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。在UNIX下有三种分别如下:

Posix有名信号量;
Posix基于内存的信号量;
System V信号量。
在这里只和大家分享下有关System V信号量。

System V通过定义计数信号量集来对信号量的操作,计数信号量集是一个或多个信号量构成一个集合,其中每个都是计数信号量。对于系统中的每个信号量集,内核维护一个如下的信息结构,它定义在<sys/sem.h>头文件中。

struct semid_ds{

struct ipc_perm sem_perm; /* operation permission struct */

struct sem *sem_base; /* ptr to array of semaphores in set */

uishort sem_nsems; /* #of semaphores in set */

time_t sem_otime; /* time of last semop() */

time_t sem_ctime; /* time of creation or last IPC_SET */

};



成员struct sem结构如下:

struct sem{

ushort_t semval; /* semaphore value , nonnegative */

short sempid; /* PID of last successful semop(), SETVAL, SETALL */

ushort_t semncnt; /* awaiting semval > current value */

ushort_t semzcnt; /* awaiting semval = 0 */

};



注意在struct semid_ds结构中的sem_base含有指向某个sem结构数组的指针:当前信号量集中的每个信号对应其中一个数组元素。我们可以把内核中的某个选定信号量图解成指向一个sem结构数组的一个semid_ds结构。图解如下:



有了以上的理论,那么接下来我们来探讨下如何对这样的信号量进行操作,linux操作系统为我们提供了操作system v信号量的API函数,以下就开始讲解这些API函数。

semget函数创建一个信号量集或访问一个已存在的信号量集。
#include <sys/sem.h>

int semget(key_t key, int nsems, int oflag);

该函数成功返回时,其返回值是一个称为信号量标识符的整数,semop和semctl使用它。出错则返回-1。

nsems参数指定集合中的信号量数。如果我们创建一个新的信号量集,而只是访问一个已存在的集合,那就可以把该参数指定为0。一旦创建完一个信号量集,我们就不能改变其中的信号量数。

oflag值是一些权限值的组合,如果是创建一个信号量集,那么得在此oflag的基础上或上O_CREAT或者是O_CREAT|O_EXCL。

此函数不可以对创建的信号量集中的信号量进行初始化,对信号量的初始化是通过另外的一个函数semctl进行的。

通过semctl函数对信号量集中的信号量进行初始化。
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …./* union semun arg*/ );

semid标识其操作待控制的信号量集。

semnum标识该信号量集内的某个成员(0, 1等待,直到nsems -1)。semnum值仅仅用于GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID命令。

第四个参数是可选的,取决于第三个参数cmd。union semnu结构如下:

union semnu{

int val; /* used for SETVAL only */

struct semid_ds *buf; /* used for IPC_SET and IPC_STAT */

ushort *array; /* used for GETALL and SETALL */

};

这个联合体并没有出现在任何头文件中,因而必须由应用程序声明。

第三个参数cmd可以取以下值:

GETVAL 把semval的当前值作为函数返回值返回。

SETVAL 把semval值设置为arg.val。如果操作成功,那么相应信号量在所有进程是的信号

量调整值(semadj)将被置为0。

IPC_RMID 把由semid指定的信号量集从系统中删除掉。

IPC_SET 设置所指定信号量集的semid_ds结构中的某些成员。

IPC_SET R 返回所指定信号量集当前的semid_ds结构。

使用semget得到一个信号量集,使用semctl设置信号量集中的信号量,那么对信号量集中的信号通过semop来进行操作。
#include <sys/sem.h>

int semop(int semid, struct sembuf *opsptr, size_t nops);

semid标识其操作的信号量集。

其中opsptr指向一个如下结构的数组:

Struct sembuf{

Short sem_num; /* semaphore number: 0, 1, …., nsems-1 */

Short sem_op; /* semaphore operation: <0, 0, >0 */

Short sem_flag; /* semaphore flags : 0, IPC_NOWAITE, SEM_UNDO */

};

nops参数指出由opsptr指向的sembuf结构数组中元素的数目。该数组中的生个元素给目标信号量集内某个特定的信号量指定一个操作。这个特定的信号量由sem)num指定,0代表第一个元素,1代表第二个元素,依次类推,直到nsems-1,其中nsems是目标信号量集内成员信号量的数目。

semop对信号的操作是由sem_op的值确定的,以下是对sem_op取值的分析:

、sem_op 为正数时,会把sem_op的值加到操作的信号量的信号值上。如果sem_flg被设置为IPC_UNDO,无论程序正常结束与否,都会把信号值重新设置为调用semop函数前得值。这对应于进程释放占用的资源数。
、sem_op为负数时,如果要操作的信号量的值大于或者等于sem_op的绝对值,则从信号量值中加上sen_op的值。如果信号量值小于sem_op的绝对值,则有如下:
如果sem_flg的值为IPC_NOWAIT,那么semop出错,返回EAGAIN。
如果sem_flg没有设置为IPC_NOWAIT,则该信号量的semncnt的值加1,然后此进程挂起,直到此信号量的值大于sem_op的绝对值,才执行semop操作;或者此信号量从系统中删除,此时semop返回EIDRM;或者该挂起进程捕捉到信号,从信号处理程序返回,此时,semop出错返回EINTR。
如果sen_op的值为0,则调用进程希望等到该信号量值变成0。
、sem_op为0时,那么调用者希望等待到semval变为0。如果sem_op已经是0,那就立即返回。


以下是利用信号量来进行一个PV操作,实现代码如下:



//初始化信号量

int init_sem(int semid, int semval)

{

SYS_NUM semnu;

semnu.sem_val = semval;

if((semctl(semid, 0, SETVAL, semnu)) < 0)

{

perror("init_sem semctl");

return -1;

}

return 0;

}



//对信号量进行p操作

int sem_p(int semid)

{

SYS_SEM sembu;



sembu.sem_num = 0;

sembu.sem_op = -1;

sembu.sem_flg = SEM_UNDO;



if((semop(semid, &sembu, 1)) < 0)

{

perror("sem_p semop");

return -1;

}

return 0;

}



//对信号量进行v操作

int sem_v(int semid)

{

SYS_SEM sembu;



sembu.sem_num = 0;

sembu.sem_op = 1;

sembu.sem_flg = SEM_UNDO;



if((semop(semid, &sembu, 1)) < 0)

{

perror("sem_v semop");

return -1;

}

return 0;

}



//删除信号量集

int del_sem(int semid)

{

if((semctl(semid, 0, IPC_RMID)) < 0)

{

perror("del_sem semctl");

return -1;

}

return 0;

}





总结:

信号量往往是用来同步的,保护共享内存。

(本文为武汉华嵌嵌入式培训所创,转载请注明来源)

...全文
202 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
hanjinchidm 2012-08-10
  • 打赏
  • 举报
回复
知道了。
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统调用 3.1 X86 CPU对中断的硬件支持 3.2 中断向量表IDT的初始化 3.3 中断请求队列的初始化 3.4 中断的响应和服务 3.5 软中断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟中断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构中的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构中的中断机制 9.5SMP结构中的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导

21,595

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 驱动开发/核心开发
社区管理员
  • 驱动开发/核心开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧