Chinaunix首页 | 论坛 | 博客
  • 博客访问: 163399
  • 博文数量: 67
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 622
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-19 19:12
文章分类

全部博文(67)

分类: LINUX

2015-08-19 17:51:49


该讨论基于linux操作系统,其他Unix系统可能不适用

信号量分有名信号量、无名信号量,此处先讨论有名信号量
1、sem_t *sem_open(const char *name, int flag, mode_t mode, unsigned int value);
    用于打开一个信号量,该信号量用参数name进行标示,指定同一个name调用sem_open()即可在多个进程间共享该信号量
    name:格式为"/abc",实测"abc"格式也可以,但是"/tmp/abc"格式不行,原因是linux会在/dev/shm目录下建立一个sem.name的文件,如果中间有'/'则不能作为文件名或者将其当成目录但该目录不存在?(开头的'/'因该是被忽略的)
    flag:主要是O_CREAT,O_EXCL,单独指定O_CREAT,若name同名信号量已存在则返回该信号量,否则创建并返回之;若指定O_CREAT|O_EXCL,则已存在的情况下出错并返回SEM_FAILED(NULL),否则创建并返回
    mode:读写权限,类似chmod命令中的权限,如660(属主和组可读写)
    value:信号量的之,二值信号量则为1
    系统为每个信号量生成一个记录,记录对其打开的次数,调用sem_open()加1,调用sem_close()减1

2、sem_wait(sem_t *sem)
    类似上锁,若当前信号量值>0,则将其值减1,否则等待
3、sem_post(sem_t *sem)
     类似解锁,将信号量值加1
4、sem_getvalue(sem_t *sem, int *vl)
    获取当前信号量的值,通过vl返回。当有进程等待在sem_wait()时*vl的值为0,无论有多少进入等待,在有的系统可能该值为负表示有几个进程在等待中。
5、sem_close(sem_t *sem)
    此函数可不调用,因为系统会在进程退出时自动调用,作用为将sem_open()调用的记录减1
6、sem_unlink(char *name)
    /dev/shm下的sem.name文件会马上被删除,但是信号量本身并没有被删除,所有已打开该信号量的进程仍能正常使用它。直到所有打开该信号量的进程sem_close()后,内核才真正删除信号量。
    如果不调用sem_unlink,则
sem.name一直存在,下次sem_open()直接从中取值sem.name文件中的值表示当前等待在信号量上的进程数(可用od命令查看),如果其值>0,则通过sem_getvalue(sem_t *sem, int *vl)返回的*vl值为0.
    如果某个进程sem_unlink()后,另一个进程再用同一个name调用sem_open(),则sem_open调用成功,但是已经是另一个信号量了,这时可能存在两个信号量,一个是没了名字没了对应文件的存在于内核中的,一个是有名字的且有对应文件。


无名信号量
必须放在共享内存区才能被非亲属关系进程间共享使用,而有名信号量不需要,非亲属进程间可以通过同一个name共享之
1、sem_init()
    不要针对一个semaphore调用超过1次,否则结果未定义,实测linux调用两次会生成一个新的,也就是两个进程各用个的。
2、sem_destroy()
    好像不起作用,即使调用了,semaphore还是可以用。猜测是内核在进程退出时才删除之。

信号量与多进程
1、亲属进程,有名信号量
    只需在父进程中调sem_open(),因为“父进程中打开的仍在子进程中打开”,下面的伪代码是对的:
    sem_t *sem = sem_open();
    fork()
    //child
    sem_wait(sem )
    //parent
    sem_wait(sem )
2、非亲属进程,有名信号量
    每个进程都需要用同一个name调用sem_open()
3、亲属进程,无名信号量
    必须放到共享内存区才能共享,所以下面的伪代码是错误的
    sem_t sem;
    sem_init(&sem, ....);

    fork()
    //child
    sem_wait(&sem)
    //parent
    sem_wait(&sem)
    父子进程间并不共享sem,而是子进程生成一个副本。也就是父子间用的不是同一个信号量。

    使用某种共享内存区技术即可实现父子间的信号量共享,伪代码如下:
    sem_t sem;
    fd = open("name", ....);
    write(fd, &sem, sizeof(sem));
    sem_t *p = mmap(0, sizeof(sem), ...,fd,..);
    sem_init(p, .....);

    fork()
    //child
    sem_wait(p)  //p作为共享内存区的地址,在父子间是共享的
    //parent
    sem_wait(p)
    其中共享内存区的实现也可以用shm_open来实现
    且也可用如下方法:省了一个sem_t的存储空间
    fd = open("name", ....);
    lseek(fd, sizeof(sem_t)-1, SEEK_SET);  

    write(fd, " ", 1); //文件长度为sizeof(sem_t),且最后一个字节为" ",必须要write否则文件长度还是0
    sem_t *p = mmap(0, sizeof(sem_t), ...,fd,..);

4、非亲属进程,无名信号量
    也要放在共享内存区,而且每个进程都要调用open()或shm_open()、mmap()等函数将共享内存区映射到当前进程的地址空间中。
    典型用法:
    进程1调用open(name, O_RDWR|O_CREAR|O_TRUNK, )、write()、mmap()、sem_init(),进程2调用open(name, O_RDWR|O_CREAR)、mmap()即可
    注意:
        即使进程1调用munmap()后,进程2仍可使用放在共享内存区的semaphore,但进程1不能用了,因为munmap()删除了进程1的映射关系,进程2的映射关系还在,而且open()打开的文件也在(终极原因)。

信号量与多线程
    多线程间自动共享内存,所以只需在主进程调用一次sem_open()、sem_init()等即可在所有线程中使用。




阅读(1450) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~