您的位置:首页 > 运维架构 > Linux

Linux 下的信号量

2014-04-30 22:28 225 查看
linux下的posix有名信号量的几个要点——博客园

最全面的linux信号量解析 ——csdn blog

Semaphore信号量总结——博客园

sem_timedwait ——csdn blog

一、信号量的概念

信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。

信号量是一个结构体,信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

二、信号量的分类

linux下的信号量有很多种,首先分为 内核信号量 和 用户态信号量。

内核信号量 :由内核控制路径使用。

用户态信号量:用户态信号量又分为 posix信号量 和system V信号量。

(system V比较复杂,头文件为#include <sys/sem.h>;

posix信号量比较简单,#include <semaphore.h>)

然后posix信号量又分为有名信号量和无名信号量。

有名信号量:其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。

无名信号量:其值保存在内存中。Linux 只实现了无名信号量。



三、POSIX信号量

1.无名信号量

无名信号量的创建就像声明一般的变量一样简单,例如:

sem_t sem_id;

然后再初始化该无名信号量,之后就可以放心使用了。

无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

首先介绍一个结构体,sem_t,它在semaphore.h中是这样定义的:

typedef struct sem_t_ * sem_t;

struct sem_t_

{

int value;

pthread_mutex_t lock;

HANDLE sem;

#if defined(NEED_SEM)

int leftToUnblock;

#endif

};

2、 无名信号相关的函数

1〉 sem_init信号量初始化函数

函数原型:

#include <semaphore.h>

int sem_init(sem_t *sem,int pshared,unisigned int value);

参数意义:

创建一个信号灯sem,初始化其值为value。

pshared决定了信号量可以在几个进程之间共享。pshared==0 用于同一多线程的同步;

若pshared>0 用于多个相关进程间的同步(即由fork产生的)。由于Linux还没有实现进程间信号灯的共献享,故此值只能为0。

2〉 sem_getvalue

函数原型:

#include<semaphore.h>

int sem_getvalue(sem_t *sem,int *sval);

参数意义:

sem :待查询的信号灯。

取回信号量sem的当前值,把该值保存到sval中。

返回值:

若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回0。

3〉 sem_wait

函数原型:

#include<semaphore.h>

int sem_wait(sem_t *sem);

参数意义:

相当于P操作,即申请资源。这是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。

返回值:

若sem>0,那么那么申请资源成功,它减1并立即返回,返回值为0。

若sem==0,则返回-1,睡眠直到sem>0,此时立即减1,然后返回。

4〉 sem_trywait

函数原型:

#include<semaphore.h>

int sem_trywait(sem_t *sem);

参数意义:

相当于P操作,即申请资源。这是一个非阻塞的函数,测试所指定信号量的值,它的操作是原子的。

返回值:

若sem>0,那么申请资源成功,它减1并立即返回,返回值为0。

若sem==0,返回-1,与sem_wait不同,不是睡眠,而是返回一个错误EAGAIN。

5〉 sem_timedwait

函数原型:

#include <semaphore.h>

int sem_timedwait(sem_t *sem,const struct timespec *abs_timeout);

参数意义:

abs_timeout 指定一个阻塞的时间上限, abs_timeout 参数指向一个指定绝对超时时刻的结构,这个结果由自 Epoch,1970-01-01 00:00:00 +0000(UTC) 秒数和纳秒数构成。一般取当前时间,再加上允许等待的时间间隔。

返回值:

如果调用时超时时刻已经到点,并且信号量不能立即锁定,那么 sem_timedwait() 将失败于超时,返回值为-1,errno 设置为 ETIMEDOUT。

如果操作能被立即执行,那么返回值为0,sem值减1, sem_timedwait() 永远不会失败于超时错误,而不管 abs_timeout 的值。进一步说,abs_timeout 的验证在此时没有进行。

6〉 sem_post

函数原型:

#include <semaphore.h>

int sem_post(sem_t *sem);

参数意义:

t相当于V操作,释放资源。把指定的信号量sem的值加1;呼醒正在等待该信号量的任意线程。

7〉 sem_destroy

函数原型:

#include <semaphore.h>

int sem_post(sem_t *sem);

参数意义:

sem_destroy 是用来删除信号灯的.

8〉 常见错误类型

a.EINTR

这个调用被信号处理器中断,参看 signal(7)。

b.EINVAL
sem 不是一个有效的信号量。

c.对 sem_trywait() 有如下额外的错误:

EAGAIN

操作不能执行而不阻塞(也就是说,信号量当前值是零)。

d.对 sem_timedwait() 有如下额外的错误:

EINVAL

abs_timeout.tv_nsecs的值小于0,或者大于等于 100 百万。

ETIMEDOUT

调用在信号量锁定之前超时。

3、 有名信号量

有名信号量的特点是把信号量的值保存在文件中。

这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。

有名信号量能在进程间共享的原因:

由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当然文件里面保存的有名信号量值就共享了。

4、 有名信号相关函数

有名信号量在使用的时候,和无名信号量共享sem_wait和sem_post函数。

区别是有名信号量使用sem_open代替sem_init,另外在结束的时候要像关闭文件一样去关闭这个有名信号量。

1〉sem_open

函数原型:

#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

函数功能:

打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完成了信号量的创建、初始化和权限的设置。

参数意义:

name是文件的路径名;

Oflag 有O_CREAT或O_CREAT|EXCL两个取值;

mode控制新的信号量的访问权限;

Value指定信号量的初始化值。

当oflag = O_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量,同时忽略mode和value参数。

当oflag = O_CREAT|O_EXCL时,若name指定的信号量已存在,该函数会直接返回error。

注意:

这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。

2〉sem_close

函数原型:

#include <semaphore.h>

sem_t *sem_close(sem_t *sem);

函数功能:

关闭信号量。

3〉sem_unlink

函数原型:

#include <semaphore.h>

sem_t *sem_unlink(sem_t *sem);

函数功能:

删除信号量。

注:在删除信号量之前,要确定所有对这个有名信号量的引用都已经通过sem_close()函数关闭了,然后只需在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量。如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作用。

也就是说,必须是最后一个使用该信号量的进程来执行sem_unlick才有效。因为每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。

5、有名信号量在无相关进程间的同步

前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于共享内存区,只有这样才能被无相关的进程所共享。

在下面这个例子中,服务进程和客户进程都使用shmget和shmat来获取得一块共享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。

四、例子

例一:无名信号量

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <time.h>

void *thread1(void*);
void *thread2(void*);
void *thread3(void*);

int alex = 0;
sem_t sem12;
sem_t sem13;

int main(int argc,char **argv)
{
pthread_t pid1,pid2,pid3;
printf("--entermain:\n");
int ret =sem_init(&sem12,0,0);
if(ret != 0)
{
printf("sem12 initFail\n");
return ret;
}
ret = sem_init(&sem13,0,0);
if(ret != 0)
{
printf("sem13 initFailed\n");
return ret;
}
pthread_create(&pid1,NULL,thread1,NULL);
pthread_create(&pid2,NULL,thread2,NULL);
pthread_create(&pid3,NULL,thread3,NULL);
sleep(6);
sem_destroy(&sem12);
sem_destroy(&sem13);
printf("--end:\n");
exit(0);
}
void *thread1(void *arg)
{
printf("this is thread1:\n");
int input;
printf("put annumber:\n");
sleep(3);
scanf("%d",&input);
printf("the number is%d\n",input);

sem_post(&sem12);
sem_post(&sem13);
printf("leavethread1\n");
pthread_exit(0);
}

void *thread2(void *arg)
{
printf("this isthread2:\n");
sem_wait(&sem12);
printf("leavethread2\n");
pthread_exit(0);
}

void *thread3(void *arg)
{
struct timespec ts;
printf("this isthread3:\n");
ts.tv_sec = time(NULL) + 5;
int s =sem_timedwait(&sem13,&ts);
if(s == -1)
printf("thread waittimeout\n");
printf("leave thread3\n");
pthread_exit(0);
}


运行结果:





例二:有名信号量

//服务器程序
/*
* test_manage_named_signal_server.c
*
* Created on: 2014/04/02
*     Author: power_user
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main(int argc,char *argv[])
{
char ch;
int shmind;
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_CREATE,0644,1);
if(mutex == SEM_FAILED)
{
perror("unable tocreate semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment withthis key
shmid =shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid < 0)
{
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 <= 'Z'; ch++)
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the bellow loop could be replaced bybinary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid,IPC_RMID,0);
exit(0);
}
//客户端程序
/*
* test_manage_named_signal_client.c
*
* Created on: 2014/04/02
*     Author: power_user
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main(int argc,char *argv[])
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;

//name the shared memory segment
key = 1000;
//create the shared memory semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
perror("reader:unableto excute semaphore ");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment withthis key
shmid = shmget(key,SHMSZ,0666);
if(shmid < 0)
{
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: Thiscan be repalced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: