信号量(一)

 

  信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

一、描述

  以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

二、特性

  抽象的来讲,信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。

三、sem_open出现段错误 名字问题

(1)posix信号灯里面的这个函数sem_open();这里面的第一个参数一般情况下第一个字符为'/'  而且后面的的字符串里面不能在中间出现反斜杠,因为这种有名信号灯的实现机制是依靠文件的方式来实现的.而反斜杠后面的为这个自定义文件的名字(也算是信号量的名字而命名机制是不允许出现反斜杠的.  (但是可以是这样,开头处连续出现多个反斜杠比如'/////semfxp',这是因为sem_open这个函数会检查第一个字符如果第一个字符为反斜杠则继续检查直到不出现反斜杠位置后面的即为名字,而如果第一个不为反斜杠,则它将默认为是信号量的名字了就直接创建这个文件了,也就不会出现问题,但是害怕的是有的人会有这种写法,'zhang/fxp', 假如说你写一个这样的名字,那麽系统会这样做,它会去在目录/dev/shm/下去找sem.zhang这个目录,结果会发现失败(之所以是sem.zhang是因为在sem_open这个源函数里面一般都添加一个sem.头部.) 当然了 你也可以在程序里面首先mkdir一个这样的文件夹比如sem.zhang   (/dev/shm/这样的话程序就不会出现问题了.

(2)

为什么sem_open("/tmp/nimeni",O_CREAT|O_EXCL,SHM_MODE,1);总是得到ENOENT

翻开sem_openman手册看看,我们大体就会知道,第一个参数有问题,以我找到了sem_open()glibc中的实现(nptl/sem_open.c):

 

  if (mountpoint.dir == NULL)

    {

      __set_errno (ENOSYS);

      return SEM_FAILED;

    }

 

  /* Construct the filename.  */

  while (name[0] == '/')

    ++name;

 

  if (name[0] == '\0')

    {

      /* The name "/" is not supported.  */

      __set_errno (EINVAL);

      return SEM_FAILED;

    }

  size_t namelen = strlen (name) + 1;

 

  /* Create the name of the final file.  */

  finalname = (char *) alloca (mountpoint.dirlen + namelen);

  __mempcpy (__mempcpy (finalname, mountpoint.dir, mountpoint.dirlen),

             name, namelen);

 

  /* If the semaphore object has to exist simply open it.  */

  if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)

    {

    try_again:

      fd = __libc_open (finalname,

                        (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);

 

//....

  所以这里的问题其实很简单,最根本的问题是那个path参数就究竟是用来干什么的?用一句话说,其实就是指定一个文件位置,这个位置是以tmpfs的挂载点为根目录的。也就是说,如果它以/开头,说明是从tmpfs根目录开始,而如果不是,就是从相对路径开始,不过当前目录也是根目录(这正是为什么sem_open()的实现中会去掉开头的/)。到了这里我们可以看出一开始那个问题的原因了,它传递的是"/tmp/nimeni",也就假设了tmpfs的挂载目录(通常是/dev/shm)下有tmp这个目录,而事实上没有,所以即使他加上了O_CREAT|O_EXCL也会返回ENOENT

所以,传递给sem_open()的第一个参数究竟怎么写才好呢?"/somename""somename"都可以,都容易读而且也都对,但是名字中间有/就不行了。

从上面我们也可以看出,对于程序员来说,有些时候啊,读文档真的还不如直接去读代码来得更快!所以这也提示我们:代码要写得像文档一样易读!我坚信好的代码就应该如此!

 

四、简单例子

 1 #include<fcntl.h>/* For O_* constants */
 2 #include<semaphore.h>
 3 #include<stdarg.h>
 4 #include <sys/stat.h>        /* For mode constants */
 5 
 6 #define    FILE_MODE    (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
 7 
 8 #ifdef    __bsdi__
 9 #define    va_mode_t    int
10 #else
11 #define    va_mode_t    mode_t
12 #endif
13 
14 #ifndef    SEM_FAILED
15 #define    SEM_FAILED    ((sem_t *)(-1))    /* avoid compiler warnings */
16 #endif
17 
18 static int Getopt(int argc, char *const *argv, const char *str)
19 {
20     int        opt;
21 
22     if ( ( opt = getopt(argc, argv, str)) == '?')
23     {
24         printf("getopt errno\n");
25         exit(1);        /* getopt() has already written to stderr */
26     }
27     return(opt);
28 }
29 
30 static sem_t * Sem_open(const char *pathname, int oflag, ...)
31 {
32     sem_t    *sem;
33     va_list    ap;
34     mode_t    mode;
35     unsigned int    value;
36 
37     if (oflag & O_CREAT) 
38     {
39         va_start(ap, oflag);        /* init ap to final named argument */
40         mode = va_arg(ap, va_mode_t);
41         value = va_arg(ap, unsigned int);
42         if ( (sem = sem_open(pathname, oflag, mode, value)) == SEM_FAILED)
43         {
44             printf("1 sem_open error for %s", pathname);
45         }
46         va_end(ap);
47     } 
48     else 
49     {
50         if ( (sem = sem_open(pathname, oflag)) == SEM_FAILED)
51         {
52             printf("2 sem_open error for %s", pathname);
53         }
54     }
55     return(sem);
56 }
57 
58 static void Sem_close(sem_t *sem)
59 {
60     if (sem_close(sem) == -1)
61     {
62         printf("sem_close error");
63     }
64 }
65 
66 
67 int main(int argc, char **argv)
68 {
69     int        c, flags;
70     sem_t    *sem;
71     unsigned int    value;
72 
73     flags =   O_CREAT | O_EXCL;
74     value = 1;
75     while ( (c = Getopt(argc, argv, "ei:")) != -1) 
76     {
77         switch (c)
78         {
79             case 'e':
80                 flags |= O_EXCL;
81                 break;
82             case 'i':
83                 value = atoi(optarg);
84                 break;
85         }
86     }
87     if (optind != argc - 1)
88     {
89         printf("usage: semcreate [ -e ] [ -i initalvalue ] <name>");
90     }
91     
92     sem = Sem_open(argv[optind], flags, FILE_MODE, value);
93 
94     Sem_close(sem);
95     exit(0);
96 }

 

[ztteng@ztteng sem]$ gcc -g semcreate.c -o semcreate -lrt
[ztteng@ztteng sem]$ ./semcreate -e -i 1 test123

 

[ztteng@ztteng shm]$ pwd
/dev/shm
[ztteng@ztteng shm]$ ls
pulse-shm-1291659894  pulse-shm-2053904295  pulse-shm-3884980878  sem.test11  sem.test123
pulse-shm-2005517518  pulse-shm-2677925162  pulse-shm-4284678624  sem.test12

可以看到在/dev/shm中看到我们创建的信号量sem.test123

 

 五、semunlink

  

 1 #include<unistd.h>
 2 #include<stdio.h>
 3 #include<stdlib.h>
 4 #include<fcntl.h>/* For O_* constants */
 5 #include<semaphore.h>
 6 #include<stdarg.h>
 7 #include <sys/stat.h>        /* For mode constants */
 8 
 9 #define    FILE_MODE    (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
10 
11 #ifdef    __bsdi__
12 #define    va_mode_t    int
13 #else
14 #define    va_mode_t    mode_t
15 #endif
16 
17 #ifndef    SEM_FAILED
18 #define    SEM_FAILED    ((sem_t *)(-1))    /* avoid compiler warnings */
19 #endif
20 
21 static void Sem_unlink(const char *pathname)
22 {
23     if (sem_unlink(pathname) == -1)
24         printf("sem_unlink error");
25 }
26 
27 int main(int argc, char **argv)
28 {
29     if (argc != 2)
30         printf("usage: semunlink <name>");
31 
32     Sem_unlink(argv[1]);
33 
34     exit(0);
35 }

[ztteng@ztteng sem]$ gcc -g semunlink.c -o semunlink -lrt

 

[ztteng@ztteng sem]$ ./semunlink /test123

把/dev/shm 中的信号量test123删除了

 

posted @ 2013-05-30 23:56  z折腾  阅读(532)  评论(0编辑  收藏  举报