- 论坛徽章:
- 0
|
Linux中的信号量
多线程应用逐步的成为商业应用的一个重要的组成部分,很难想象任何非多线程的成熟的商业应用,这些应用都要使用多线程技术用于提升系统或是应用的性能。尽管这样,事情往往都不是那么的美好,就像多线程特性的使用同样也导致了一系列的问题例如:死锁,竞态条件,多线程的不正常行为等等。为了解决这些问题,操作系统提供了一系列的方法比如:互斥锁(mutex)、信号量(semaphores)、信号和内存屏障机制。这篇文章将讨论这些方式中的一种-信号量,并且给出它的一些内部机制。
信号量介绍:
信号量可以被看作是表示资源状态的一个简单的计数器,这个计数器是一个受系统保护的变量而不能被用户直接访问,对它访问的限制正是由内核提供的。信号量的使用很简单,如果这个计数器大于零,表示这个资源是可用的,如果计数器小于等于0表示这个资源正忙,或是被其它的所使用。这种简单的机制用于同步基于多线程或是多进程的应用。信号量是由Edsger Dijkstra发明和提出的,并且仍然在当今的操作系统中作为同步的机制广泛使用。同样的机制现在也可以被应用程序的开发者使用,它是进程间通讯的一个重要的组成部分。
根据共享的资源的数目信号量即可以用于二元(binary)也可以用于计数,如果只有一个单一的共享资源被使用,我们就只需要一个信号量用于同步的目的。在这种情况下,这个信号量作为二元信号量使用,在其它的情况下,供使用的资源的数目大于1,我们使用被称为计数信号量的多元信号量。
信号有两种基本的操作:一种等待信号量变量,另外一种通知信号量变量。其实信号量只是一种计数器,下面的算法描述了这两种信号量操作:
假设:
s是信号量变量
W(s)标识为等待这个信号量
P(s)意思为通知这个信号量可用
算法:
W(s) 算法:
while (s
//do nothing
}
s=s-1;
P(s) 算法:
s=s+1;
从上面的算法可以容易理解等待一个信号只是把信号量计数器减一,通知信号是完全相反的过程,把信号量计数加一。
下面让我们看一些在linux内核里面用于实现信号量功能的结构和函数,信号量使用下面两个结构体:
struct semaphore
{
atomic_t count;
int sleepers;
wait_queue_head_t wait;
}
struct rw_semaphore
{
_s32 activity
spinlock_t wait_lock;
struct list_head wait_list;
};
这个结构体在较新的内核里面已经做了些改变,它包括一些附加的变量,如下面所示:
struct rw_semaphore {
signed long count;
#define RWSEM_UNLOCKED_VALUE 0x00000000
#define RWSEM_ACTIVE_BIAS 0x00000001
#define RWSEM_ACTIVE_MASK 0x0000ffff
#define RWSEM_WAITING_BIAS (-0x00010000)
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS) spinlock_t wait_lock;
struct list_head wait_list;
#if RWSEM_DEBUG
int debug;
#endif
};
struct rw_semaphore {
signed long count;
#define RWSEM_UNLOCKED_VALUE 0x00000000
#define RWSEM_ACTIVE_BIAS 0x00000001
#define RWSEM_ACTIVE_MASK 0x0000ffff
#define RWSEM_WAITING_BIAS (-0x00010000)
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS) spinlock_t wait_lock;
struct list_head wait_list;
#if RWSEM_DEBUG
int debug;
#endif
};
内核级别的用于实现信号操作的基本操作可以在/asm/semaphore.h和/asm/rwsem.h头文件中找到:
· __down(struct semaphore *):这个函数检查信号量是否大于1, 如果大于1, 它减少信号量的值然后返回, 如果不大于1, 它处于休眠状态并稍后重试。
· __up(struct semaphore *): 这个函数增加信号量计数,并唤醒任何等待到这个信号量上的线程。
· __down_trylock(struct semaphore *): 这个函数检查是否信号量是可用的,如果不可用,这个函数讲直接返回,所以它被列为非阻塞函数。
· __down_interruptible(struct semaphore *): 这个函数的行为和__down类似只是有一点不同,它可以被信号中断,当它被中断后它将返回-ENINTR。而__down在运行中是阻塞信号的。
· 其它的一些函数如__down_read(锁住信号量并且读),__down_write(锁住信号量并且写), __up_read(读后释放信号量),__up_write(写后释放信号量)这些函数允许多个读进程去读一个受保护的资源,但是只有一个写进程可以去更新它。
信号量的发行版本
当前的Unix环境中有两个类型的信号量:system V和posix,通常较老的基于unix系统使用system V版本,当前基于linux的系统使用posix版本。尽管这样,信号量通常的行为和实现技术在不管哪种版本上都没有改变,让我们看一下接口和它们在这两个不同版本的信号量上是如果工作的。
System V信号量
System V信号量的接口和使用由于不必要的复杂性而显得有些混乱。例如:被创建的信号量并不只是一个计数器(值)而是一个信号量计数器(值)的集合。它引入了一个信号量集的概念,即使用一个标识信号量id的集合,这个集合包括0到n个信号量。实现这个功能的函数是:
int semget(key_t key, int nsems, int semflg);
Key 使用的信号量标识
Nsems 在这个集合中需要的信号量数目
Semflg 标识了信号量应该如果的被创建,它应该是下面类型中的一个:
o IPC_CREAT: 如果这个可以还不存在,那么创建这个新的信号量。
o IPC_EXCL: 如果这个信号量存在,将导致这个函数失败.
Key的类型是key_t,可以被用户、程序员或通过ftok()系统调用赋予任何有意义的值,然而System V信号量提供了一个不同的key,它被标识为IPC_PRIVATE。当这个key被使用的时候,每次调用semget,它都会创建一个新的标识为返回的信号量id的信号量集合。下面的代码片段展示了如何在每次调用semget函数的时候创建一个新的信号量。
{
int semid;
semid=semget(IPC_PRIVATE, 1,IPC_CREAT);
if (semid
{
perror("Semaphore creation failed Reason:");
}
}
注意:实际上IPC_PRIVATE被定义为((__key_t) 0).这个定义可以在ipc.h中找的。
一旦一个信号量被创建,System V需要这些在一个信号量集中创建的信号量被初始化。信号量的创建和初始化并不是原子操作,所以需要用户另外的完成初始化工作。完成这个功能的函数如下:
int semctl(int semid, int semnum, int cmd, ...);
Semid 信号量的标识符
Semnum 这个semid标识的集合中信号量的个数
cmd定义了在这个信号量上所做操作的类型,对于初始化的目的,这个标识使用SETVAL。对于其它的值请参考man手册。根据选择的"cmd"参数需要,第四个类型为union semun的参数应该被传入,但是这是一个可选择的参数。如果使用这个结构,用户必须显式地像下面定义:
union semun {
int val;
struct semid_ds *buf;
ushort *array;
} arg
下面的代码片段设置了semid标识的信号集中的第一个信的值为1。
{
semun init;
init.val = 1;
int i = semctl(semid, 1, SETVAL, init);
}
一旦一个信号量被创建和初始化完成,用户就开始准备在这个信号量集合上进行操作。同样,像锁住和解锁信号这些用于操作信号量的接口也不是很简单的。在调用它们之前需要写一些代码,用于实现在一个信号量上所希望的操作。
int semop(int semid, struct sembuf *sops, size_t nsops);
Semid 信号量的标识符
Sops 是一个用户定义的信号量结构体数组,尽管文档建议这个结构体不能被扩展,这个结构体定义如下:
struct sembuf {
ushort sem_num; /* identifies which semaphore in the set */
short sem_op; /* could be positive,negative or zero */
short sem_flg; /*coud be IPC_NOWAIT ,SEM_UNDO*/
};
Nsops 限定被传入的sembuf个数,当许多同时对多个信号量进行操作的情况下这个参数应该被提供。
Sembuf结构体的sem_op这个成员变量是锁住和解锁信号量的一个重要的值,它可以被赋予下面的值,这些值决定了对信号量所做出操作的类型:
1. 如果sem_op是零,它将等待信号量的值变成0。
2. 如果sem_op是正数,它将以sem_op的绝对值递增信号量的值。
3. 如果sem_op是负数,它将以sem_op的绝对值递减信号量的值。
你可能看到当上面的sem_op的值是正数的时候意味着释放一个信号量,当它是负数的时候意味着去锁住一个信号量。注意:信号量的值不能是负数。
在创建、初始化和操作信号量之后最重要的操作就是释放信号量和它占用的内存空间,这个可以在一个清理句柄或是析构函数中,当退出正常的代码或是应用异常退出的时候被调用。如果不进行清理操作,应用程序在某个时候可能找不到足够的信号量去工作,因为它们在system V实现中被定义为系统范围值(system-wide)。
下面的代码片段清理一个semid标识的信号量集合。
{
int ret = semctl(semid,0,IPC_RMID); //removing the 0th semaphore in the set
}
这里的IPC_RMID标志表示了需要在semid这个信号量进行的操作类型。
下面这个可以工作的例子使用上面提及的概念和函数:
#include
#include
#include
#include
//create user defined semun for initializing the semaphores
void *Thread1(void *arg)
{
int semid;
semid = (int)arg;
//in order to perform the operations on semaphore
// first need to define the sembuf object
struct sembuf op1,op2;
//operation for 0th semaphore
op1.sem_num = 0; //signifies 0th semaphore
op1.sem_op = -1; //reduce the semaphore count to lock
op1.sem_flg = 0; //wait till we get lock on semaphore
//operation for 1th semaphore
op2.sem_num = 1; //signifies 0th semaphore
op2.sem_op = -1; //reduce the semaphore count to lock
op2.sem_flg = 0; //wait till we get lock on semaphore
//locking the 0th semaphore
if (semop(semid,&op1,1) == -1)
{
perror("Thread1:semop failure Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread1:Successfully locked 0th semaphore\n");
//lock the 1th semaphore
if (semop(semid,&op2,1) == -1)
{
perror("Thread1:semop failure Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread1:Successfully locked 1th semaphore\n");
//release the 0th semaphore
op1.sem_num = 0; //signifies 0th semaphore
op1.sem_op = 1; //reduce the semaphore count to lock
op1.sem_flg = 0; //wait till we get lock on semaphore
if (semop(semid,&op1,1) == -1)
{
perror("Thread1:semop failure Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread1:Successfully unlocked 0th semaphore\n");
//release the 1th semaphore
op2.sem_num = 1; //signifies 0th semaphore
op2.sem_op = 1; //reduce the semaphore count to lock
op2.sem_flg = 0; //wait till we get lock on semaphore
if (semop(semid,&op2,1) == -1)
{
perror("Thread1:semop failure Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread1:Successfully unlocked 1th semaphore\n");
}
void *Thread2(void *arg)
{
int semid;
semid = (int)arg;
//in order to perform the operations on semaphore
// first need to define the sembuf object
struct sembuf op1,op2;
//operation for 0th semaphore
op1.sem_num = 0; //signifies 0th semaphore
op1.sem_op = -1; //reduce the semaphore count to lock
op1.sem_flg = 0; //wait till we get lock on semaphore
//operation for 1th semaphore
op2.sem_num = 1; //signifies 0th semaphore
op2.sem_op = -1; //reduce the semaphore count to lock
op2.sem_flg = 0; //wait till we get lock on semaphore
//lock the 0th semaphore
if (semop(semid,&op1,1) == -1)
{
perror("Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread2:Successfully locked 0th semaphore\n");
//lock the 1th semaphore
if (semop(semid,&op2,1) == -1)
{
perror("Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread2:Successfully locked 1th semaphore\n");
//release 0th semaphore
op1.sem_num = 0; //signifies 0th semaphore
op1.sem_op = 1; //reduce the semaphore count to lock
op1.sem_flg = 0; //wait till we get lock on semaphore
if (semop(semid,&op1,1) == -1)
{
perror("Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread2:Successfully unlocked 0th semaphore\n");
//release the 1th semaphore
op2.sem_num = 1; //signifies 0th semaphore
op2.sem_op = 1; //reduce the semaphore count to lock
op2.sem_flg = 0; //wait till we get lock on semaphore
if (semop(semid,&op2,1) == -1)
{
perror("Reason:");
exit(-1);
}
else
fprintf(stderr,"Thread2:Successfully unlocked 1th semaphore\n");
}
int main()
{
pthread_t tid1,tid2;
int semid;
//create user defined semun for initializing the semaphores
typedef union semun
{
int val;
struct semid_ds *buf;
ushort * array;
}semun_t;
semun_t arg;
semun_t arg1;
//creating semaphore object with two semaphore in a set
//viz 0th & 1th semaphore
semid = semget(IPC_PRIVATE,2,0666|IPC_CREAT);
if(semid
{
perror("semget failed Reason:");
exit(-1);
}
//initialize 0th semaphore in the set to value 1
arg.val = 1;
if ( semctl(semid,0,SETVAL,arg)
{
perror("semctl failure Reason:");
exit(-1);
}
//initialize 1th semaphore in the set to value 1
arg1.val = 1;
if( semctl(semid,1,SETVAL,arg1)
{
perror("semctl failure Reason: ");
exit(-1);
}
//create two threads to work on these semaphores
if(pthread_create(&tid1, NULL,Thread1, semid))
{
printf("\n ERROR creating thread 1");
exit(1);
}
if(pthread_create(&tid2, NULL,Thread2, semid) )
{
printf("\n ERROR creating thread 2");
exit(1);
}
//waiting on these threads to complete
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
//once done clear the semaphore set
if (semctl(semid, 1, IPC_RMID ) == -1 )
{
perror("semctl failure while clearing Reason:");
exit(-1);
}
//exit the main threads
pthread_exit(NULL);
return 0;
}
注意:System V信号量由用户控制,对于开发者来说信号量的权限是一个问题。维护这些信息的结构体是struct semid_ds.
struct semid_ds
{
struct ipc_perm sem_per; /* operation's permission structure */
struct sem * sem_base; /* pointer to first sem in a set */
ushort sem_nsems; /* number of sem in a set */
time_t sem_otime; /*time of last semop */
time_t sem_ctime; /* time of last change */
};
对于系统中的每个信号量的集合,内核都在sys/ipc.h中维护一个相关信息的结构体。为获取上面给出的信号量的详细信息,IPC_STAT标志可以被使用并用传入一个semid_ds的指针被传入。获取和设置权限的成员变量是sem_per,让我们看一个简单的例子:
#include
#include
#include
#include
int main()
{
int semid;
struct semid_ds status;
semid = semget(( key_t )0x20,10,IPC_CREAT|0666);
if(semid == -1)
{
perror("sem creation failed:Reason");
exit(0);
}
//get the permission details
semctl(semid,0,IPC_STAT,&status);
printf("owners uid is %u\n",status.sem_perm.uid);
printf("group uid is %u\n",status.sem_perm.gid);
printf("Access mode is %c\n",status.sem_perm.mode);
//set the permissions details
status.sem_perm.uid = 102;
status.sem_perm.gid = 102;
status.sem_perm.mode = 0444;
semctl(semid,0,IPC_SET,&status);
return 1;
}
注意:被设置的新权限只允许是以前权限的一个子集而不能是超集。
作者:
Vikram Shukla
有七年半使用面向对象语言进行开发和设计的经验,现在在新泽西的Logic Planet担任顾问。
原文地址:http://linuxdevcenter.com/pub/a/linux/2007/05/24/semaphores-in-linux.html?page=1
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/106913/showart_2119182.html |
|