与POSIX信号量比较System V信号量潜在的学习曲线要高得多,当阅读完这个部分并和上面System V的部分对比,你就会更加的这么认为。
首先,POSIX使用简单的语义去创建、初始化和对信号量进行操作。它们提供了一个有效的方式来处理进程间通讯。POSIX有两种类型的信号量,有名信号量和无名信号量。
有名信号量
如果你看man手册,你将会看到一个有名信号量像systemv信号量一样由一个名字标识,并且类似的这个信号量也有内核存留。这就意味着这些信号量像systemV一样是系统范围值并且在任何时候可存活并被限制一定的值之内。有名信号的优点是它们提供了一种在不相关进程和相关进程(如线程)间的同步机制。
一个有名信号可以通过调用下面的函数创建:
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
Name
用于标识信号量的名字
Oflag
被设置为O_CREAT用来创建一个信号量(如果和0_EXCL一起,当这个信号量已经存在时候这个调用将会失败)
mode_t
控制新的信号量的访问权限
Value
指定信号量的初始化值
与svstemV的方式截然不同的是它使用一个单一的调用就完成了信号量的创建、初始化和权限的设置。自然它也是更清楚和具有原子性的。另外一个不同是systemV信号量通过一个int类型的值来标识自己(类似于调用open()返回的fd),而sem_open函数返回sem_t类型作为posix信号量的标识值。
从现在起,都是对信号量所要进行的操作,锁住一个信号量的语义是:
int sem_wait(sem_t *sem);
这个调用将锁住信号量,如果这个信号量的计数是大于0,锁住这个信号量之后,信号量计数减一。如果这个信号量的计数是0,这个调用被阻塞。
解锁一个信号量的语义是:
int sem_post(sem_t *sem);
这个调用对信号量增1然后返回。
一旦你使用了一信号量,销毁它们就变得很重要。在做这个之前,要确定所有对这个有名信号量的引用都已经通过sem_close()函数关闭了,然后只需在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量,注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作用。
无名信号量
根据man手册,一个无名信号量被放置在一个被多个线程(多线程共享信号量)或是进程(多进程共享信号量)共享的内存区域。一个线程共享信号量被放置在一个只有同一个进程的线程共享它们的区域,例如一个全局的变量。一个进程共享的信号量被放置在一个不同进程可以共享它们的区域,例如一个共享内存区。一个无名信号量为线程间和相关的进程间提供同步机制。
无名信号量不需要使用sem_open调用,下面的两行代码替换了它的行为:
{
sem_t semid;
int sem_init(sem_t *sem, int pshared, unsigned value);
}
Pshared:这个参数用来标识这个信号量是在一个进程的线程间共享还是在进程之间共享,如果pshared这个标识是0,表示这个信号在一个进程的线程内部共享,如果它是一个非零的值,那么它在不同进程之间共享。
Value:表示这个信号量被初始化的值。
一旦这个信号被初始化,程序员就可以去操作这个sem_t类型的信号量。信号量的锁操作和解锁操作如下面所展示的:sem_wait(sem_t *sem)和sem_post(sem_t *sem)。删除一个无名信号量之需要调用sem_destroy函数。
这个文章的最后的部分有个使用POSIX信号量开发的简单的生产者和消费者的例子。
System V信号量和Posix信号量的比较:
1 System V信号量和Posix信号量实现的一个显著的不同是:对于System V信号量你可以控制每次自增或是自减的信号量计数,而在Posix里面,信号量计数每次只能自增或是自减
Posix信号不允许操作信号量的权限,而对于System V信号允许你将信号量权限改为以前信号量的一个子集。
2. 在POSIX信号量里面创建和初始化是原子的(从使用者的角度)。
3. 从使用的角度,System V信号量是复杂的,而Posix信号量是简单。
4. 和System V信号量相比Posix信号量的可扩展性(使用无名信号量)是更好的,在一个用户/客户情景中,需要每个用户创建它自己的服务器示例,使用Posix模型讲更好。
5. 对于System V信号量当它创建一个信号量对象时,要创建一个信号量数组;而Posix信号量只是创建一个,鉴于这个特性,在System V里面信号量(内存占有量)与Posix相比是代价更高的。
6. 据说Posix信号量的性能比System V信号量更好。
7. Posix信号量提供一种进程宽度而不是系统宽度的信号量机制(无名信号量),所以如果一个开发者忘记去关掉这个信号量,在进程的退出的情况下这个信号量将会被清理,简言之,Posix信号量提供一种非常驻的信号量机制。
理解信号量作用
信号量较之其它同步机制的优点是它们可以用于去同步两个试着去访问同一个资源的相关或是不相关的进程。
相关进程:
如果进程是从一已经存在的进程创建,并最终操作这个创建进程的资源,那么这些进程被称为相关的。下面的例子展示了相关进程是如果被同步的。
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd, i,count=0,nloop=10,zero=0,*ptr;
sem_t mutex;
//open a file and map it into memory
fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);
write(fd,&zero,sizeof(int));
ptr = mmap(NULL,sizeof(int),PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
/* create, initialize semaphore */
if( sem_init(&mutex,1,1)
{
perror("semaphore initilization");
exit(0);
}
if (fork() == 0) { /* child process*/
for (i = 0; i
sem_wait(&mutex);
printf("child: %d/n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
/* back to parent process */
for (i = 0; i
sem_wait(&mutex);
printf("parent: %d/n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
在这个例子中,相关进程通过信号量被同步的访问一个一般内存块。
不相关进程:
如果两个进程互相不知道或者它们之间没用关系存在则被称为不相关进程。例如,两个不同的程序实体就是不相关进程。如果这样程序试着访问一个共享的资源,信号量可以被用来去同步它们的访问。下面的代码对这个进行了演示:
u>File1: server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
perror("unable to create semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid
{
perror("failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start writing into memory
s = shm;
for(ch='A';ch
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
_exit(0);
}
File 2: client.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
perror("reader:unable to execute semaphore");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid
{
perror("reader:failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start reading
s = shm;
for(s=shm;*s!=NULL;s++)
{
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
上面的可执行程序(客户端和服务器端)演示了信号量是如何在完全不同进程之间使用的。除了上面的应用,信号量也可以用于合作地访问一个资源。请注意信号量不是Mutex,Mutex允许串行地访问一个资源,而信号量除了可以串行访问之外,也可以用于并行访问资源。例如,考虑资源R正在被n个使用者访问,使用Mutx,我们需要一个Mutex "m"去锁住和解锁资源,因此在某个时候只允许一个使用者去使用资源R。相比之下,信号量允许n个用户去同步访问资源R。最常见的例子就是洗漱间模型(
Toilet Example
)。
信号量另外一个优点是可以用在这样的场合:开发者需要限制一个可执行程序执行或是映射一个内存的次数,让我们看一个简单的例子:
#include
#include
#include
#include
#include
#define KEY 0x100
typedef union semun
{
int val;
struct semid_ds *st;
ushort * array;
}semun_t;
int main()
{
int semid,count;
struct sembuf op;
semid = semget((key_t)KEY,10,0666|IPC_CREAT);
if(semid==-1)
{
perror("error in creating semaphore, Reason:");
exit(-1);
}
count = semctl(semid,0,GETVAL);
if(count>2)
{
printf("Cannot execute Process anymore/n");
_exit(1);
}
//get the semaphore and proceed ahead
op.sem_num = 0; //signifies 0th semaphore
op.sem_op = 1; //reduce the semaphore count to lock
op.sem_flg = 0; //wait till we get lock on semaphore
if( semop(semid,&op,1)==-1)
{
perror("semop failed : Reason");
if(errno==EAGAIN)
printf("Max allowed process exceeded/n");
}
//start the actual work here
sleep(10);
return 1;
}
信号量和互斥量(Mutex)的不同:
1. 一个信号量可以称为互斥量,但是一个互斥量不可能成为信号量。这意味这个一个二元信号量可以作为Mutex使用,但是一个互斥量永远不能展示出信号量的功能。
2. 信号量和互斥量(至少对于最新的内核)都是不可重入的。
3. 没有进程持有信号量,而Mutex是被持有的,并且持有者是需要对它们复杂的,从调试的角度看这是一个重要的不同。
4. 对于信号量,持有它的线程有责任去释放它,而对于信号量这是不需要的,任何的线程都可以使用sem_post()函数去释放信号量。
5. 一个互斥量被用于串行的访问一个可重入的代码,而这段代码是不能被超过一个线程并发执行的;一个信号量限制了同时访问一个共享资源的最大数量。
6. 一个对于开发者来说另外一个重要的不同是信号量是系统级的并且在文件系统中以文件形式存在,除非它被清理,而互斥量是进程级的,当进程退出时它们会被自动的销毁。
7. 信号量的性质使得它可以用于相关进程和不相关进程同步事件,也包括线程间。互斥量只能用于线程间,至多是相关线程间(最新的内核中线程的实现引入了一种特性允许互斥量在相关进程间使用)。
8. 根据内核文档,互斥量相比于信号量是更轻量级的。这意味着使用信号量的程序相比与使用互斥量的程序有更高的内存占有量。
9.从使用的角度,互斥量相比与信号量有更简单的语义。
生产者-消费者问题
生产者-消费者问题是用来验证信号量作用的一个古老的模型,让我们看一个传统的生产者-消费者问题和它的解决方案,这里所展示的情景并不是很复杂。
这里有两个进程:一个生产者和一个消费者,生产者向一个数据区里面插入数据,而消费者从数据区中删除数据。这里必须有足够的空间给生成者向数据区插入信息,生产者有一个单独的函数向数据区插入数据,类似的消费者有一个单独的函数从数据区删除数据。简言之,生产者依赖消费者在数据区制造足够的空间以便其可以插入更多的信息,而消费者依靠生产者向数据区插入数据以便使用和删除这些信息。
要开发这个模型,需要一个允许生产者和消费者进行通讯的机制,以便它们知道什么时候从数据区写入或是读出信息是安全的,用于实现这一机制的就是信号量。
在下面的简单的代码中,数据区被定义为char buffer[BUFF_SIZE]并且缓存的大小是#define BUFF_SIZE 4。生产者和消费着都可以访问这个大小是4的数据区,Posix信号量在这里被使用作为通知机制。
#include
#include
#include
#define BUFF_SIZE 4
#define FULL 0
#define EMPTY 0
char buffer[BUFF_SIZE];
int nextIn = 0;
int nextOut = 0;
sem_t empty_sem_mutex; //producer semaphore
sem_t full_sem_mutex; //consumer semaphore
void Put(char item)
{
int value;
sem_wait(&empty_sem_mutex); //get the mutex to fill the buffer
buffer[nextIn] = item;
nextIn = (nextIn + 1) % BUFF_SIZE;
printf("Producing %c ...nextIn %d..Ascii=%d/n",item,nextIn,item);
if(nextIn==FULL)
{
sem_post(&full_sem_mutex);
sleep(1);
}
sem_post(&empty_sem_mutex);
}
void * Producer()
{
int i;
for(i = 0; i
{
Put((char)('A'+ i % 26));
}
}
void Get()
{
int item;
sem_wait(&full_sem_mutex); // gain the mutex to consume from buffer
item = buffer[nextOut];
nextOut = (nextOut + 1) % BUFF_SIZE;
printf("/t...Consuming %c ...nextOut %d..Ascii=%d/n",item,nextOut,item);
if(nextOut==EMPTY) //its empty
{
sleep(1);
}
sem_post(&full_sem_mutex);
}
void * Consumer()
{
int i;
for(i = 0; i
{
Get();
}
}
int main()
{
pthread_t ptid,ctid;
//initialize the semaphores
sem_init(&empty_sem_mutex,0,1);
sem_init(&full_sem_mutex,0,0);
//creating producer and consumer threads
if(pthread_create(&ptid, NULL,Producer, NULL))
{
printf("/n ERROR creating thread 1");
exit(1);
}
if(pthread_create(&ctid, NULL,Consumer, NULL))
{
printf("/n ERROR creating thread 2");
exit(1);
}
if(pthread_join(ptid, NULL)) /* wait for the producer to finish */
{
printf("/n ERROR joining thread");
exit(1);
}
if(pthread_join(ctid, NULL)) /* wait for consumer to finish */
{
printf("/n ERROR joining thread");
exit(1);
}
sem_destroy(&empty_sem_mutex);
sem_destroy(&full_sem_mutex);
//exit the main thread
pthread_exit(NULL);
return 1;
}
总结:
我们已经探讨信号量不同变种的可能性和它与互斥量之间的区别,当开发人员在System V和Posix信号量之间转移或决定是使用Mutex还是信号量时这些详细的知识将会是有用的。对于上面例子的API的更信息请参考相关的man手册。
引用:
http://www.dcs.ed.ac.uk/home/adamd/essays/ex1.html
http://www.die.net/doc/linux/man/man7/sem_overview.7.html
http://www.csc.villanova.edu/~mdamian/threads/posixsem.html
http://www.cim.mcgill.ca/~franco/OpSys-304-427/lecture-notes/node31.html
Linux man pages
Linux Kernel Documentation
作者:
Vikram Shukla
有七年半使用面向对象语言进行开发和设计的经验,现在在新泽西的Logic Planet担任顾问。