linux system v信号量,POSIX信号量和SYSTEM V信号量(1)

本文详细介绍了POSIX信号量和System V信号量的概念与使用方法。包括有名信号量与无名信号量的区别,以及如何通过信号量实现进程间同步。还提供了丰富的代码示例,帮助读者更好地理解信号量的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

信号量

信号量分有名和无名信号量。它们的区别和管道及命名管道的区别类似。有名信号量要求创建一个文件,而无名信号量则直接保存在内存中。

一,Posix信号量

Po***信号量接口总结(见下图):

上面一行是有名信号量,可于fifo相类比,其值保存在文件中,可用于进程和线程同步;

下面一行是无名信号量,可与pipe相类比,其值保存在内存中,可用于进程和线程同步;

中间部分,是两者的公用接口。

sem_open()                                sem_close(),sem_unlink()  //有名信号量

/ |sem_wait(),sem_post()       |/

/ |sem_trywait(),sem_getvalue()|/sem_destroy()  //无名信号量

sem_init()

1.公共接口

1.1 接口函数说明

#include int sem_wait(sem_t *sem);

测试所指定信号量的值,它的操作是原子的。

若sem>0,那么它减1并立即返回。

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

int sem_trywait(sem_t *sem);

其他的行为和sem_wait一样,除了:

若sem==0,不是睡眠,而是返回一个错误EAGAIN。

int sem_post(sem_t *sem);

把指定的信号量sem的值加1;

呼醒正在等待该信号量的任意线程。

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

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

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

1) 返回0

2) 返回阻塞在该信号量上的进程或线程数目

linux采用返回的第一种策略。

注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数。

1.2 接口使用的一般流程

sem_init(&sem);

sem_wait(&sem);

critical area;

sem_post(&sem);

remainder area

2.无名信号量

无名信号量是保存在变量类型为sem_t的内存中。

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

1)pshared==0 用于同一多线程的同步;

2)若pshared>0 用于多个进程间的同步,此时sem必须放在共享内存中。

int sem_destroy(sem_t *sem);

只能销毁由sem_init初始化的信号量,否则后果不可预料也。

例1:

多线程使用信号量的简单例子:

/*

* simple_sem_app.c

*/

#include "all.h"

/* 每个字符输出的间隔时间 */

#define TEN_MILLION 5000000L

#define BUFSIZE 1024

void *threadout(void *args);

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

{

int error;

int i;

int n;

sem_t semlock;

pthread_t *tids;

if (argc != 2) {

fprintf (stderr, "Usage: %s numthreads/n", argv[0]);

return 1;

}

n = atoi(argv[1]);

tids = (pthread_t *)calloc(n, sizeof(pthread_t));

if (tids == NULL) {

perror("Failed to allocate memory for thread IDs");

return 1;

}

if (sem_init(&semlock, 0, 1) == -1) {

perror("Failed to initialize semaphore");

return 1;

}

for (i = 0; i < n; i++) {

if (error = pthread_create(tids + i, NULL, threadout, &semlock)) {

fprintf(stderr, "Failed to create thread:%s/n", strerror(error));

return 1;

}

}

for (i = 0; i < n; i++) {

if (error = pthread_join(tids[i], NULL)) {

fprintf(stderr, "Failed to join thread:%s/n", strerror(error));

return 1;

}

}

return 0;

}

void *threadout(void *args)

{

char buffer[BUFSIZE];

char *c;

sem_t *semlockp;

struct timespec sleeptime;

semlockp = (sem_t *)args;

sleeptime.tv_sec = 0;

sleeptime.tv_nsec = TEN_MILLION;

snprintf(buffer, BUFSIZE, "This is thread from process %ld/n",

(long)getpid());

c = buffer;

/****************** entry section *******************************/

while (sem_wait(semlockp) == -1)

if(errno != EINTR) {

fprintf(stderr, "Thread failed to lock semaphore/n");

return NULL;

}

/****************** start of critical section *******************/

while (*c != '/0') {

fputc(*c, stderr);

c++;

nanosleep(&sleeptime, NULL);

}

/****************** exit section ********************************/

if (sem_post(semlockp) == -1)

fprintf(stderr, "Thread failed to unlock semaphore/n");

/****************** remainder section ***************************/

return NULL;

}

说明:该例子来自于usp。

可以把sem_wait和sme_post调用去掉,看看效果,可以看到出现了交叉输出的情况。

nanosleep调用只是为了让输出的效果更明显,没有其他意义。

更多的例子见mypxsem/prodcons2-4.c

3. 有名信号量

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

如下面的形式:

sem_t *mutex;

...

mutex = sem_open(pathname, O_CREAT | O_EXCL, FILE_MODE, 0);

if ((childpid = fork()) == 0) {

/* child */

...

sem_wait(mutext);

...

}

/* parent */

...

sem_post(mutex);

...

3.1 常用函数说明

sem_t *sem_open(const char *name, int oflag,

mode_t mode, unsigned int value);

返回一个sem_t类型的指针。该指针随后可用作sem_close等的参数。

该函数参数的详细信息,可以参考手册。

int sem_close(sem_t *sem);

关闭sem信号量,并释放资源。

int sem_unlink(const char *name);

在所有进程关闭信号量后删除name的信号量

3.2 有名信号量的使用

例子:

/*

* chainname.c

*/

#include "my_unpipc.h"

#define BUFSIZE 1024

#define PERMS (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define FLAGS (O_CREAT | O_EXCL)

static int getnamed(char *name, sem_t **sem, int val);

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

char buffer[BUFSIZE];

char *c;

pid_t childpid = 0;

int delay;

volatile int dummy = 0;

int i, n;

sem_t *semlockp;

if (argc != 4){       /* check for valid number of command-line arguments */

fprintf (stderr, "Usage: %s processes delay semaphorename/n", argv[0]);

return 1;

}

n = atoi(argv[1]);

delay = atoi(argv[2]);

for (i = 1; i < n; i++)

if ((childpid = fork()) > 0)    /* father break */

break;

snprintf(buffer, BUFSIZE,

"i:%d  process ID:%ld  parent ID:%ld  child ID:%ld/n",

i, (long)getpid(), (long)getppid(), (long)childpid);

c = buffer;

if (getnamed(argv[3], &semlockp, 1) == -1) {

perror("Failed to create named semaphore");

return 1;

}

while (sem_wait(semlockp) == -1)                         /* entry section */

if (errno != EINTR) {

perror("Failed to lock semlock");

return 1;

}

while (*c != '/0') {                                  /* critical section */

fputc(*c, stderr);

c++;

for (i = 0; i < delay; i++)

dummy++;

}

if (sem_post(semlockp) == -1) {                           /* exit section */

perror("Failed to unlock semlock");

return 1;

}

if (wait(NULL) == -1)                              /* remainder section */

return 1;

return 0;

}

static int getnamed(char *name, sem_t **sem, int val)

{

while (((*sem = sem_open(name, FLAGS , PERMS, val)) == SEM_FAILED) &&

(errno == EINTR)) ;

if (*sem != SEM_FAILED)

return 0;

if (errno != EEXIST)

return -1;

while (((*sem = sem_open(name, 0)) == SEM_FAILED) && (errno == EINTR)) ;

if (*sem != SEM_FAILED)

return 0;

return -1;

}

以上代码创建了一个进程链,若把sem_wait和sem_post调用去掉,可以看到输出很混乱。

这是由于每个子进程都共享了父进程的文件表项,而且都指向打开的文件表项。

system v 信号量

===============

1, 该类信号量,与posix信号量不同。它表示的信号量集,而不是单个信号量。

可用于不同进程间的同步。

内核为每个信号量集,维护一个如下的信息结构:struct semid_ds {

struct ipc_perm sem_perm;    /* 信号量集的操作许可权限 */

struct sem *sem_base;        /* 某个信号量sem结构数组的指针,

当前信号量集中的每个信号量对应其中一个数组元素 */

ushort sem_nsems;            /* sem_base 数组的个数 */

time_t sem_otime;            /* 最后一次成功修改信号量数组的时间 */

time_t sem_ctime;            /* 成功创建时间 */

};

struct sem {

ushort semval;        /* 信号量的当前值 */

short  sempid;        /* 最后一次返回该信号量的进程ID号 */

ushort semncnt;        /* 等待semval大于当前值的进程个数 */

ushort semzcnt;        /* 等待semval变成0的进程个数 */

};

2, 信号量操作函数

a. 创建和打开信号量

int semget(key_t key, int nsems, int oflag)

(1) nsems>0  : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。

(2) nsems==0 : 访问一个已存在的集合

(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。

(4) 创建成功后一下结构被设置:

.sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID

.oflag 参数中的读写权限位存入sem_perm.mode

.sem_otime 被置为0,sem_ctime被设置为当前时间

.sem_nsems 被置为nsems参数的值

.而于该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。

b. 设置信号量的值

int semop(int semid, struct sembuf *opsptr, size_t nops);

(1) semid 是semget返回的semid

(2) nops : 是数组opsptr的个数

(3) opsptr : 是操作结构的数组

struct sembuf {

short sem_num;    /* 信号量的数目: 0,1,...,nsems-1 */

short sem_op;    /* 信号量操作 */

short sem_flg;  /* 操作表示符 */

};

(4) 若sem_op 是正数,其值就加到semval上;

若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就反回;

若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值.

(5) sem_flg

SEM_UNDO     由进程自动释放信号量

IPC_NOWAIT  不阻塞

c. 对信号量集实行控制操作

int semctl(int semid, int semnum, int cmd, ../* union semun arg */);

其中semid是信号量集合,semnum是信号在集合中的序号,

union semun

{

int val; /* cmd == SETVAL */

struct semid_ds *buf /* cmd == IPC_SET或者 cmd == IPC_STAT */

ushort *array; /* cmd == SETALL, 或 cmd = GETALL */

};

cmd是控制命令,参数可选

cmd取值如下:

GETVAL, SETVAL : semid集合中semnum信号量当前的semval值

GETALL,SETALL :semid集合中所有信号量的值。

IPC_RMID:删除semid信号量集

GETPID:返回最后成功操作该信号的进程号。

IPC_STAT:返回semid集合中的struct semid_ds结构。

例子:

/* my_sem.c */

#include #include #include #include #include #include #include int main (int argc, char **argv)

{

key_t ipckey;

int semid;

/*建立两个信号灯结构*/

struct sembuf sem[2]; /* sembuf defined in sys/sem.h */

/* 创建IPC Key */

ipckey = ftok("/tmp/rich", 42);

/* 创建信号量. 4 == READ, 2 == ALTER */

semid = semget(ipckey, 1, 0666 | IPC_CREAT);

if (semid < 0)

{

printf("Error - %sn", strerror(errno));

_exit(1);

}

/*设置*/

/* These never change so leave them outside the loop */

sem[0].sem_num = 0;

sem[1].sem_num = 0;

sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */

sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */

while(1)

{

printf("[%s] Waiting for the semaphore to be releasedn/n", argv[1]);

/* 设置两个信号灯,灯1等待,灯2请求资源锁 */

sem[0].sem_op = 0; /* Wait for zero */

sem[1].sem_op = 1; /* Add 1 to lock it*/

/*设置信号量集,两个信号量*/

semop(semid, sem, 2);

/*资源锁区*/

printf("[%s] I have the semaphoren/n", argv[1]);

sleep(rand() % 3);

/* Critical section, sleep for 0-2 seconds */

sem[0].sem_op = -1; /* Decrement to unlock */

/*出锁,对信号量1操作*/

semop(semid, sem, 1);

printf("[%s] Released semaphoren/n", argv[1]);

sleep(rand() % 3); /* Sleep 0-2 seconds */

}

}

阅读(1579) | 评论(0) | 转发(2) |

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值