ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 信号量

信号量

原创 Linux操作系统 作者:liiinuuux 时间:2016-04-05 17:30:02 0 删除 编辑
信号量实现的是课堂上“操作系统原理”里提到的经典PV操作
即:

点击(此处)折叠或打开

  1. semaphore s = 1;
  2. ...
  3. P(s);
  4. critical code (临界区)
  5. V(s);
  6. ...

信号量可以近似理解为博物馆里的互动体验区:
体验区有一个初始的机器数;
每来一个人,机器数就减少1;
每离开一个人,机器数就加1;
当机器数降到0时,新来的人就必须等待有人离开才能得到机器;
与互动体验区 不同的是,信号量通常用来实现串行机制,因此初始数量通常是1。这样被信号量保护的一小段代码在同一时间就只能被一个线程执行。

linux里使用信号量需要三个函数

点击(此处)折叠或打开

  1. #include
  2. #include
  3. #include

  4. // 创建信号量
  5. int semget(key_t key, int nsems, int semflg);
  6. // 控制信号量
  7. int semctl(int semid, int semnum, int cmd, ...);
  8. // 实现PV操作
  9. int semop(int semid, struct sembuf *sops, unsigned nsops);


创建信号量
int semget(key_t key, int nsems, int semflg);
key:自定义一个整数。这个参数类似open函数的第一个参数,由调用者指定一个“文件名”。
nsems:要初始化多少个信号量,通常设为1。
semflg:这个参数也和open函数类似。支持权限和IPC_CREAT以及IPC_EXCL
     IPC_CREAT表示要创建一个信号量,但是如果信号量已经存在了也不会报错。
     IPC_EXCL和IPC_CREAT一起使用时,如果信号量已经存在就会报EEXIST。
     通常可以将semflg设为IPC_CREAT|IPC_EXCL|0666等

例子
#include
#include
#include
#include
#include
#include
#include
#include
int init_sem(int key, int val)
{
    int id = semget((key_t)key, 1, IPC_CREAT|0666|IPC_EXCL);
    if(id < 0)
    {
        if(errno == EEXIST)
        {
            printf("sem %d exists ... ", key);
            if((id = semget((key_t)key, 1, 0)) > 0)
                printf("get existed sem %d.\n", key);
        }
    }
    else
    {
        printf("new sem %d created.\n", key);
    }
    return id;
}

int main()
{
    int pid = getpid();
    printf("pid %d started\n", pid);
    int semid = init_sem(9999, 1);
    printf("sem id %d\n", semid);
}

程序指定key为9999,这个是随意的。
第一次执行时,用“IPC_CREAT|0666|IPC_EXCL ”可以成功,会提示创建成功。
[root@server2 sem]# ./a
pid 5273 started
new sem 9999 created.
sem id 360451

此时用ipcs -s可以看到创建的信号量。即使程序推出了,这个信号量也会留在系统里。
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x00000000 0          root       600        1         
0x00000000 32769      root       600        1         
0x1d00b184 163842     oracle     640        154       
0x0000270f 360451     root       666        1 

再次执行程序时,由于指定的是相同的key,系统会发现已经存在这个信号量了。
[root@server2 sem]# ./a
pid 5319 started
sem 9999 exists ... get existed sem 9999.
sem id 360451


控制信号量
int semctl(int semid, int semnum, int cmd, ...);
semid:有semget返回的id
semnum:由semid指向的第几个信号量。由于semid是唯一标识,因此semnum通常是0,也就是第1个。
cmd:进行何种操作。支持的操作比较多,常用的有:
     GETVAL:设置信号量的值,semctl(sem_id, 0, GETVAL);
     IPC_RMID:删除信号量,semctl(sem_id, 0, IPC_RMID);
     SETVAL:设置信号量的值,这个需要用到semctl的第四个参数
第四个参数是一个union,根据cmd不同赋不同的值。
要使用第四个参数,需要先自定义这个union
#ifndef _SEMUN_H
#define _SEMUN_H
union semun
{
  int val;               // value for SETVAL
  struct semid_ds *buf;      // buffer for IPC_STAT & IPC_SET
  unsigned short int *array;     // array for GETALL & SETALL
  struct seminfo *__buf;     // buffer for IPC_INFO
};
#endif
然后就可以SETVAL了:
union semun sem_union;
sem_union.val = 1;
semctl(sem_id, 0, SETVAL, sem_union); 


实现PV操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:semget返回的id。
sops:要进行的操作的地址。是个数组,但通常只有一个元素。
nsops:要进行几项操作,通常都是1。

描述操作的结构体:
struct sembuf
{
  unsigned short int sem_num;   /* semaphore number */
  short int sem_op;     /* semaphore operation */
  short int sem_flg;        /* operation flag */
};
其中:
sem_num是这个元素在数组里的下表,只有一个就写0。
sem_op是要给信号量加或减多少。
sem_flg通常设置为SEM_UNDO,这样当系统异常退出,系统可以回退进程对信号量的操作。

简单的PV操作
int sem_p(int id)
{
    struct sembuf buf = {0, -1, SEM_UNDO};
    return semop(id, &buf, 1);
}
int sem_v(int id)
{
    struct sembuf buf = {0, 1, SEM_UNDO};
    return semop(id, &buf, 1);
}

完整例子
#include
#include
#include
#include
#include
#include
#include
#include

#ifndef _SEMUN_H
#define _SEMUN_H
union semun
{
  int val;               // value for SETVAL
  struct semid_ds *buf;      // buffer for IPC_STAT & IPC_SET
  unsigned short int *array;     // array for GETALL & SETALL
  struct seminfo *__buf;     // buffer for IPC_INFO
};
#endif

int init_sem(int key, int val)
{
    int id = semget((key_t)key, 1, IPC_CREAT|0666|IPC_EXCL);
    if(id < 0)
    {
        if(errno == EEXIST)
        {
            printf("sem exists ... ");
            if((id = semget((key_t)key, 1, 0)) > 0)
            {
                printf("get existed sem %d ", id);
                int ret = semctl(id, 0, GETVAL);
                printf("with value %d\n", ret);
            }
        }
    }
    else
    {
        printf("new sem %d created ... ", id);
        union semun sun;
        sun.val = val;
        int ret = semctl(id, 0, SETVAL, sun);
        if(ret == 0)
        {
            printf("initial value %d.\n", val);
        }
        else return ret;
    }
    return id;
}

int sem_p(int id)
{
    struct sembuf buf = {0, -1, SEM_UNDO};
    return semop(id, &buf, 1);   
}

int sem_v(int id)
{
    struct sembuf buf = {0, 1, SEM_UNDO};
    return semop(id, &buf, 1);   
}

int getsem(int id)
{
    return semctl(id, 0, GETVAL);
}

int main()
{
    int pid = getpid();
    printf("pid %d started\n", pid);
    int semid = init_sem(9999, 1);

    if(semid < 0)
    {
        printf("%s\n", strerror(errno));
    }

    char c = 0;
    while(1)
    {
        if((c = getchar()) == 'q') return 1;
        printf("sem value = %d.  P ... ", getsem(semid));
        sem_p(semid);
        printf("ok, sem value = %d.\n", getsem(semid));

        if((c = getchar()) == 'q') return 1;
        printf("sem value = %d.  V ... ", getsem(semid));
        sem_v(semid);
        printf("ok, sem value = %d.\n", getsem(semid));
    }   

}


使用方法
开两个shell
shell 1
[root@server2 sem]# ./a
pid 5800 started
new sem 458755 created ... initial value 1.

shell 2
[root@server2 sem]# ./a
pid 5803 started
sem exists ... get existed sem 458755 with value 1

shell 1
按回车
sem value = 1.  P ... ok, sem value = 0.

shell 2 
按回车,陷入等待

shell 1
再按回车
sem value = 0.  V ... ok, sem value = 0.

shell 2
此时P操作完成,显示
sem value = 0.  P ... ok, sem value = 0.

最后,不管两个进程是什么状态,推出后系统都会回退进程所做的操作。信号量变回1。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/26239116/viewspace-2075663/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: epoll
下一篇: 共享内存
请登录后发表评论 登录
全部评论

注册时间:2012-11-12

  • 博文量
    94
  • 访问量
    353449