学习笔记09-学习《精通UNIX下C语言编程及项目实践》

本文深入探讨了信号量机制,介绍了信号量的概念、作用及其在进程间通信中的应用。文章详细解释了信号量的创建、控制及操作方法,并通过具体实例展示了如何使用信号量实现进程间的同步和互斥。

十三章 信号量

  进程间的通信不仅仅包括数据交流, 也包括过程控制.

  信号量是一个可以用来控制进程存储共享资源的计数器, 它可以是跟踪共享资源的生产和消费的计数器, 也可以是协调资源的生产者和消费者之间的同步器, 还可以是控制生产进程和消费进程的互斥开关.

  信号量简介

  操作系统通过信号量和PV操作, 可以完成同步和互斥操作.

  信号量集合由一个或多个信号量集合组成, IPC对象中的'信号量'通常指的是信号量集合, UNIX的内核采用结构semid_ds来管理信号量, 结构如下:

struct semid_ds {

        struct ipc_perm sem_perm;               /* permissions .. see ipc.h */

        __kernel_time_t sem_otime;              /* last semop time */

        __kernel_time_t sem_ctime;              /* last change time */

        struct sem      *sem_base;              /* ptr to first semaphore in array */

        struct sem_queue *sem_pending;          /* pending operations to be processed */

        struct sem_queue **sem_pending_last;    /* last pending operation */

        struct sem_undo *undo;                  /* undo requests on this array */

        unsigned short  sem_nsems;              /* no. of semaphores in array */

};

  指针sem_base指向一个信号量数组,信号量由结构sem记载,如下所示:

Struct sem{

    unsigned short semval;       // 信号量取值

    pid_t sempid;              // 最近访问进程ID

    unsigned short semncnt;      // P阻塞进程数

    unsigned short semzcnt;      // Z阻塞进程数

}

  Shell中可以通过'ipcs -a -s'命令查询系统中信号量的基本信息.

  (1) 信号量的创建

  UNIX, 函数semget用来创建信号量, 原型如下:

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

  函数semget创建一个新的信号量, 或者访问一个已经存在的信号量.

  (2) 信号量的控制

  系统调用semctl用来控制信号量, 原型如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

  函数semctl对标识号为semid的信号量集合中序号为semnum的信号量进行赋值, 初始化, 信息获取和删除等多相操作, 参数cmd指定了操作的类型, 参数arg指定了函数输入输出的缓冲区, 定义如下:

union semun {

        int val;                        /* value for SETVAL */

        struct semid_ds *buf;           /* buffer for IPC_STAT & IPC_SET */

        unsigned short *array;          /* array for GETALL & SETALL */

        struct seminfo *__buf;          /* buffer for IPC_INFO */

        void *__pad;

};

  函数semctl的第四个参数arg在本质上是一个4字节的缓冲区. 调用失败时返回-1并置errno.

  本处设计一个类似于命令'ipcs'和命令'ipcrm'的程序ipcsem, 它从命令行参数中获取要执行的操作, 包括创建信号量, 读取信号量信息, 读取信号量取值和删除信号量等, 程序如下:

#include <sys/sem.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/stat.h>

#include <stdio.h>

 

#define VerifyErr(a, b)  /

        if (a) fprintf(stderr, "%s failed./n", (b));  /

        else fprintf(stderr, "%s success./n", (b));

 

int main(int argc, char *argv[1])

{

       int semid, index, i;

        unsigned short array[100];

        struct semid_ds ds;

       

        if(argc != 4)

                 return 0;

        semid = atoi(argv[1]);

        index = atoi(argv[2]);

        if(argv[3][0] == 'c'){

                 VerifyErr(semget(semid, index, 0666|IPC_CREAT|IPC_EXCL) < 0, "Create sem");

        }

        else if(argv[3][0] == 'd'){

                 VerifyErr(semctl(semid, 0, IPC_RMID, NULL) < 0, "Delete sem");

        }

        else if(argv[3][0] == 'v'){

                 fprintf(stderr, "T    ID    INDEX    SEMVAL    SEMIPID    SEMNCNT    SEMZCNT/n");

                 fprintf(stderr, "s %6d %6d %10d %10d %10d %10d/n", semid, index,

                          semctl(semid, index, GETVAL), semctl(semid, index, GETPID),

                          semctl(semid, index, GETNCNT), semctl(semid, index, GETZCNT));

        }

        else if(argv[3][0] == 'a'){

                 ds.sem_nsems = 0;

                 VerifyErr(semctl(semid, 0, IPC_STAT, &ds) != 0, "Get Sem Stat");

                 VerifyErr(semctl(semid, 0, GETALL, array) != 0, "Get Sem All");

                 for(i=0;i<ds.sem_nsems;i++)

                          fprintf(stderr, "sem no [%d]: [%d]/n", i, array[i]);

        }

        else

                 VerifyErr(semctl(semid, index, SETVAL, atoi(argv[3])) != 0, "Set Sem Val");

 

        return 0;

}

  执行结果如下:

[bill@billstone Unix_study]$ make ipcsem

cc     ipcsem.c   -o ipcsem

[bill@billstone Unix_study]$ ipcs -s

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

0x000003e8 0          bill      666        10

 

[bill@billstone Unix_study]$ ./ipcsem 2000 2 c

Create sem success.

[bill@billstone Unix_study]$ ipcs -s

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

0x000003e8 0          bill      666        10

0x000007d0 65537      bill      666        2

 

[bill@billstone Unix_study]$ ./ipcsem 65537 0 100

Set Sem Val success.

[bill@billstone Unix_study]$ ./ipcsem 65537 0 v

T    ID    INDEX    SEMVAL    SEMIPID    SEMNCNT    SEMZCNT

s  65537      0        100      23829          0          0

[bill@billstone Unix_study]$ ./ipcsem 65537 1 200

Set Sem Val success.

[bill@billstone Unix_study]$ ./ipcsem 65537 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [100]

sem no [1]: [200]

[bill@billstone Unix_study]$ ./ipcsem 65537 0 d

Delete sem success.

[bill@billstone Unix_study]$ ipcs -s

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

0x000003e8 0          bill      666        10

 

[bill@billstone Unix_study]$

  操作信号量

  信号量具有P, VZ三种操作, UNIX, 这些操作可以通过函数semop调用完成, 函数semop可以一次性操作同一信号量集合中的多个信号量. 原型如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

  函数semop对标识号为semid的信号量集合中的一个或多个信号量执行信号数值的增加, 减少或比较操作. 参数sops指向一个sembuf结构的缓冲区, nsops指定了缓冲区中存储的sembuf结构的个数.

struct sembuf {

        unsigned short  sem_num;        /* semaphore index in array */

        short           sem_op;         /* semaphore operation */

        short           sem_flg;        /* operation flags */

};

  其中, 第一个信号的序号是0. sem_op指定了操作的类型:

  a) 正数. V操作.

  b) 负数. P操作.

  c). Z操作. 判断信号量数值是否等于0.

  sem_flg取值有IPC_NOWAITSEM_UNDO.

  下面是一个用于临界资源的读写控制和并发进程的同步和互斥控制的实例: 假设进程A是生产者, 进程B是消费者, 系统最多只能同时容纳5个产品, 初始成品数为0. 当产品数不足5时允许进程A生产, 当产品数超过0时允许进程B消费.

  这里需要两个信号量模拟生产-消费过程. 信号量A代表了当前生产的数目, 它控制了生产者进程A, 信号量n代表当前尚有n个成品可以生产.

  信号B代表了当前的产品数, 他控制消费者进程B, 当信号量为n时剩余n个产品.

  生产者进程sema.c如下:

[bill@billstone Unix_study]$ cat sema.c

#include <sys/sem.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <sys/stat.h>

 

#define VerifyErr(a,b) /

        if (a) { fprintf(stderr, "%s failed./n", (b)); exit(1); } /

        else fprintf(stderr, "%s success./n", (b));

 

int main(void)

{

        int semid;

        struct sembuf sb;

 

        VerifyErr((semid = semget(2000, 2, 0666)) < 0, "Open Sem 2000");

        sb.sem_num = 0;

        sb.sem_op = -1;

        sb.sem_flg &= ~IPC_NOWAIT;

        VerifyErr(semop(semid, &sb, 1) != 0, "P sem 2000:0");

        fprintf(stderr, "[%d] producing ... ... /n", getpid());

        sleep(1);

        fprintf(stderr, "[%d] produced/n", getpid());

        sb.sem_num = 1;

        sb.sem_op = 1;

        sb.sem_flg &= ~IPC_NOWAIT;

        VerifyErr(semop(semid, &sb, 1) != 0, "V sem 2000:1");

 

        return 0;

}

[bill@billstone Unix_study]$

  消费者进程semb.c如下:

[bill@billstone Unix_study]$ cat semb.c

#include <sys/sem.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <sys/stat.h>

 

#define VerifyErr(a,b) /

        if (a) { fprintf(stderr, "%s failed./n", (b)); exit(1); } /

        else fprintf(stderr, "%s success./n", (b));

 

int main(void)

{

        int semid;

        struct sembuf sb;

 

        VerifyErr((semid = semget(2000, 2, 0666)) < 0, "Open Sem 2000");

        sb.sem_num = 1;

        sb.sem_op = -1;

        sb.sem_flg &= ~IPC_NOWAIT;

        VerifyErr(semop(semid, &sb, 1) != 0, "P sem 2000:1");

        fprintf(stderr, "[%d] consuming ... ... /n", getpid());

        sleep(1);

        fprintf(stderr, "[%d] consumed/n", getpid());

        sb.sem_num = 0;

        sb.sem_op = 1;

        sb.sem_flg &= ~IPC_NOWAIT;

        VerifyErr(semop(semid, &sb, 1) != 0, "V sem 2000:1");

 

        return 0;

}

[bill@billstone Unix_study]$

  编译程序并使用之前的程序ipcsem创建信号量集合:

[bill@billstone Unix_study]$ ./ipcsem 2000 2 c

Create sem success.

[bill@billstone Unix_study]$ ipcs -s

 

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

0x000003e8 0          bill      666        10

0x000007d0 98305      bill      666        2

 

[bill@billstone Unix_study]$ ./ipcsem 98305 0 5

Set Sem Val success.

[bill@billstone Unix_study]$ ./ipcsem 98305 1 0

Set Sem Val success.

[bill@billstone Unix_study]$ ./ipcsem 98305 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [5]

sem no [1]: [0]

[bill@billstone Unix_study]$

  在一个终端上运行sema:

[bill@billstone Unix_study]$ ./ipcsem 98305 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [3]

sem no [1]: [2]

[bill@billstone Unix_study]$ ./sema

Open Sem 2000 success.

P sem 2000:0 success.

[23940] producing ... ...

[23940] produced

V sem 2000:1 success.

[bill@billstone Unix_study]$ ./ipcsem 98305 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [2]

sem no [1]: [3]

[bill@billstone Unix_study]$

  在另一个终端上执行semb:

[bill@billstone Unix_study]$ ./ipcsem 98305 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [2]

sem no [1]: [3]

[bill@billstone Unix_study]$ ./semb

Open Sem 2000 success.

P sem 2000:1 success.

[23942] consuming ... ...

[23942] consumed

V sem 2000:1 success.

[bill@billstone Unix_study]$ ./ipcsem 98305 0 a

Get Sem Stat success.

Get Sem All success.

sem no [0]: [3]

sem no [1]: [2]

[bill@billstone Unix_study]$

 读者可以试着连续执行sema几次,观察进程的P阻塞状态。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值