ISSN1009-3044
E-mail:xsjl@cccc.net.cn电脑知识与技术ComputerKnowledgeandTechnologyhttp://www.dnzs.net.cn
Tel:+86-551-56909635690964
基于Linux的信号量通信机制研究与实现
袁玉锦,周群(邯郸学院网络中心,河北邯郸056005)
摘要:该文以信号量通信理论为基础,通过对Linux信号量相关系统调用的分析,着重讨论了内核级和用户级的信号量通信、同一进程内线程之间的通信、多用户的进程间的通信等问题,并采用ANSIC编写了信号量通信的具体实例。关键词:信号量;Linux;多进程通信;线程通信中图分类号:TP316
文献标识码:A
文章编号:1009-3044(2010)12-3279-03
1信号量通信理论
操作系统原理中利用信号量来解决多进程互斥访问临界资源的问题,还可来描述多进程之间的前趋关系,即同步问题。信号量的概念由荷兰学者E.W.Dijkstra于1965年提出。信号量实际是一个整数,进程(也可以是线程)在信号量上的操作分2种,一种称为P操作,另一种称为V操作。P操作是让信号量的值减1,V操作是让信号量的值加1。在进行实际的操作之前,进程首先检查信号量的当前值,如果当前值大于0,则可以执行P操作,否则进程休眠,等待其他进程在该信号量上的V操作,因为其他进程的V操作将让信号量的值增加,从而它的P操作可以成功完成。某信号量在经过某个进程的成功操作之后,其他休眠在该信号量上的进程就有可能成功完成自己的操作,这时系统负责检查休眠进程是否可以完成自己的操作。
在操作系统中,信号量最简单的形式也是最初被提出时定义的形式是一个整数,多个进程可检查并设置信号量的值。这种检查并设置(Test-and-Set)操作是不可中断的,也称为“原子”操作。检查并设置操作的结果是信号量的当前值和设置值相加的结果,该设置值可以是正值,也可以是负值。根据检查并设置操作的结果,进行操作的进程可能会进入休眠状态,而当其他进程完成自己的检查并设置操作后,由系统检查前一个休眠进程是否可以在新信号量值的条件下完成相应的检查并设置操作。这样,通过信号量,就可以协调多个进程的操作,实现多进程之间通信。
操作系统原理中通常把信号、信号量通信称为低级通信,而把管道、消息队列、共享存储区通信称为高级通信。信号量分为有名、无名两种,进程间通信用有名信号量,同一进程内部通信一般用无名信号量。
2Linux中的信号量
从意义和实现机理上,UnixSystemV或Linux的信号量与以上所述的常规信号量没有什么区别,但SystemV提供的信号量机制要复杂得多,并且分为两种不同系统调用类型:一种是用内核级的信号量,有关系统调用在/usr/include/semaphore.h中包含,一般可用于内核级的进程通信和内核级的线程通信;另一种是用户级信号量,有关系统调用在/usr/include/sys/sem.h中包含,一般可用于多用户进程之间通信。
2.1内核级的信号量相关系统调用
intsem_init(sem_t*sem,intpshared,unsignedintvalue)
作用:为单个信号量设置初值,第一参数*sem为指定的信号量对象;第二参数pshared为共享标志,如值为0表示私有信号量,非0表示可以与其他进程共享的信号量;第三参数value为要为信号量设置的初值。
相关数据结构如下:
struct{struct{longintstatus;
intspin_lock;}sem_lock;
intsem_value;
pthread_descrsem_waiting;}sem_t
intsem_wait(sem_t*sem);
作用:对指定的信号量进行P操作。Intsem_post(sem_t*sem);
作用:对指定的信号量进行V操作。
总结:以上是内核级信号量通信常用到的三个系统调用,使用方式较为简单,但主要适用于内核级多线程之间通信,后面将给出多线程通信的具体实例。
收稿日期:2010-02-21
作者简介:袁玉锦(1980-),女,河北曲周人,邯郸学院网络中心,助教,学士,研究方向为计算机网络;周群(1981-),女,河北武安
人,助教,学士,主要研究方向为计算机网络。
本栏目责任编辑:谢媛媛
系统软件与软件工程
3279
ComputerKnowledgeandTechnology电脑知识与技术
第6卷第12期(2010年4月)
2.2用户级信号量相关系统调用
intsemget(key_tkey,intnsems,intsemflg);
作用:创建一个新的信号量组或获取一个已经存在的信号量组。返回值为此信号量组的id,第一参数key是系统内表示此组信号量的名字,其值在系统内必须是唯一的整型数,可以人工给定,也可以通过使用其他系统调用自动产生;第二参数nsems表示要创建的信号量组中包含几个信号量;第三参数表示此信号量组的使用权限,即_rwxrwxrwx方式,使用时采用模数方式,如0777表示所有者、同组人和其他人具有所有权限。
intsemop(intsemid,structsembuf*sop,intnsops);
作用:可以一次对一个或多个信号量进行P,V操作。第一参数为由semget()返回的信号量组的id;第二参数为一个较为复杂的数据结构,给sop->sem_op赋负值表示P操作,给sop->sem_op赋正值表示V操作;第三参数nsops表示要对此信号量组中的哪个信号量实施P,V操作。
Intsemctl(intsemid,intsemnum,intcmd,semunarg);
作用:可以用来获取一些信号量的使用信息或者对信号量赋初值。第一参数为由semget()返回的信号量组的id;第二参数为要控制的信号量个数;第三参数为控制命令;第四参数的arg.array用来存放信号量的值。
总结:以上是用户级信号量通信常用到的三个系统调用,使用方式较为复杂,但主要适用于多用户进程之间的通信。后面将给出2个用户进程通信的具体实例。
3信号量通信实例
3.1经典“生产者/消费者”问题
这一问题可以描述如下:两个进程共享一个公共的、固定大小的缓冲区。其中一个进程,即生产者,向缓冲区放入信息,另外一个进程,即消费者,从缓冲区中取走信息(该问题一般也可以化为m个生产者和n个消费者)。当生产者向缓冲区放入信息时,如果缓冲区是满的,则生产者进入休眠,而当消费者从缓冲区中拿走信息后,可唤醒生产者;当消费者从缓冲区中取信息时,如果缓冲区为空,则消费者进入休眠,而当生产者向缓冲区写入信息后,可唤醒消费者。
以下为主要程序代码,一个线程插入1到10000的整数,另一个线程读取并打印。
void*producer(void*data){for(n=0;n<10000;n++)
{sem_wait(sem_write);/*P操作*/
buffer[writepos]=n;/*在缓冲区中保存一个整数*/writepos++;
If(writepos>=BUFFER_SIZE)writepos=0;
sem_post(sem_read);/*V操作;表示缓冲区中有数据*/}}
void*consumer(void*data){while(1)
{sem_wait(sem_read);/*P操作*/
data=buffer[readpos];/*从缓冲区读取数据*/printf(″-->%d\\n″,date);readpos++;
if(readpos>=BUFFER_SIZE)b->readpos=0;sem_post(sem_write);}}
intmain(void)
{sem_tsem_write,sem_read;/*可读取元素个数和可写入的空位个数*/pthread_tth_a,th_b;/*定义线程对象*/
sem_init(sem_write,0,BUFFER_SIZE_1);/*初始化为BUFFER_SIZE_1*/sem_init(sem_read,0,0);/*初始化为0*/
readpos=writepos=0;/*初始化为缓冲区读写位置*//*建立生产者和消费者线程。*/
pthread_create(&th_a,NULL,producer,0);pthread_create(&th_b,NULL,consumer,0);/*等待生产者和消费者结束。*/pthread_join(th_a,&retval);pthread_join(th_b,&retval);}
程序首先建立2个线程分别扮演生产者和消费者的角色。生产者负责将1到10000的整数写入缓冲区,而消费者负责从同一
个缓冲区中读取并删除由生产者写入的整数。因为生产者和消费者是2个同时运行的线程,并且要使用同一个缓冲区进行数据交换,因此必须利用信号量机制实现同步。起初程序初始化了2个信号量,分别表示可读取的元素数目和可写入的空位个数,并分别初始化为0和缓冲区大小减1。生产者首先对sem_write进行P操作(即sem_wait调用),看是否能够写入,如果此时sem_write信
3280
系统软件与软件工程本栏目责任编辑:谢媛媛
第6卷第12期(2010年4月)
ComputerKnowledgeandTechnology电脑知识与技术
号量的值大于零,则sem_wait可以立即返回,否则生产者将在该sem_write信号量上等待。生产者在将数据写入之后,在sem_read信号量上进行V操作(即sem_post调用)。此时如果有消费者等待在sem_read信号量上,则可以被系统唤醒而继续运行。消费者线程的操作恰恰相反,首先在sem_read上进行P操作,当读取数据并打印后,在sem_write信号量上进行V操作。
通过上面的例子可见,SystemV或Linux线程(轻量级进程)之间的信号量通信机制是内核级的,其使用的函数在/usr/include/
semaphore.h中说明。以上代码经修饰后,可在Linux下编译运行,编译方式为$gcc-oprod_cons.exe-lpthreadprod_cons.c。所包含的
头文件为 3.2多用户进程通信 Linux为一个多用户、多任务操作系统,经常涉及到多个用户在不同的终端上运行同一个程序,这时就是多进程并发运行。为简单起见,以下代码适用于2个用户进程的通信,可以在Linux下使用Alt+F1和Alt+F2切换登录2个不同用户并运行。 以下为此程序主要代码,创建一个包含2个信号量的信号量组,2个进程交替运行。 main(intargc,char*argv){structsembufs0,s1; semid=semget(75,2,0777|IPC_CREAT);/*创建一组(2个)信号量*/init[0]=0;init[1]=1;if(argc==1) {semctl(semid,2,SETALL,init);/*给两个信号量赋初值*/s0.sem_op=-1; s0.sem_flg=SEM_UNDO; s0.sem_num=0;/*预备对第0个信号量P操作*/s1.sem_op=1; s1.sem_flg=SEM_UNDO; s1.sem_num=1;/*预备对第1个信号量V操作*/for(i=1;i<=3;i++) {semop(semid,&s0,1);/*对第0个信号量实施P操作*/printf(″进程0第%d步\\n″,i); semop(semid,&s1,1);/*对第1个信号量实施P操作*/}}else {semid=semget(75,2,0777|IPC_CREAT);/*创建一组(2个)信号量*/s0.sem_op=-1; s0.sem_flg=SEM_UNDO; s0.sem_num=1;/*预备对第1个信号量P操作*/s1.sem_op=1; s1.sem_flg=SEM_UNDO; s1.sem_num=0;/*预备对第0个信号量V操作*/for(i=1;i<=3;i++){semop(semid,&s0,1); printf(″进程1第%d步\\n″,i);semop(semid,&s1,1);}}} 此例代码经修饰后,可在Linux下编译运行,编译方式为$gcc-oonebyone.exeonebyone.c。所包含的头文件为 4总结 第一类信号量通信使用简单,创建信号量与给信号量赋初值是原子操作,不可中断,一次创建一个信号量,比较适合于一个进程内的多个线程通信,所以其信号量类型可以为私有;第二类信号量通信数据结构比较复杂,一次创建一组信号量,创建信号量和给信号量赋初值是分开操作的,P,V操作使用相同的系统调用,比较适合于多个进程之间的通信,所以其信号量权限必须为多个用户都可访问。 参考文献: [1]汤子赢,哲凤屏,汤小丹.计算机操作系统[M].西安:西安电子科技大学出版社,2000.[2]RichardStevensW.UNIX环境高级编程[M].北京:机械工业出版社,2000.[3]陈莉君.Linux操作系统内核分析[M].北京:人民邮电出版社,2000. [4]RichardPetersen.LinuxProgrammer'sReference[M].北京:电子工业出版社,2001. 本栏目责任编辑:谢媛媛系统软件与软件工程 3281 因篇幅问题不能全部显示,请点此查看更多更全内容