Linux编程之信号量

本文介绍了Linux编程中用于多进程同步的信号量机制,包括POSIX和System V两种API。POSIX信号量有命名和匿名两种,使用sem_open、sem_init等函数操作。System V信号量是一个集合,通过semget、semctl等函数管理。文中通过实例展示了两种信号量的使用步骤和区别。

在Linux编程中,通过fork调用创建子进程从而实现多进程非常容易,应用也非常广泛。多进程之间要保护临界区资源,方法之一就是使用信号量。Linux中的信号量API有两套,一套是POSIX标准的API,另一套则是较老的System V API。POSIX API遵循POSIX标准,因此移植性更好,即便是非UNIX/Linux系统,只要支持POSIX标准,代码也可编译运行;而System V API存在时间更长,因此可能存在的更广泛。下面分别介绍这两种API。

POSIX信号量

POSIX信号量有两种形式:

  • 命名信号量。命名信号量又一个名字标识,名字的形式为"/somename",即一个斜杠再加一些字母(只能又一个斜杠)。命名信号量可以用sem_open来创建或者打开,可以被多个进程同时操作。
  • 匿名信号量。匿名信号量则没有名字标识,根据使用范围,分为线程间共享的匿名信号量和进程间共享的匿名信号量。进程间共享的匿名信号量通常放在全局变量中;而进程间共享的信号量则放在共享内存中。匿名信号量使用sem_init函数进行初始化。

POSIX信号量相关的API如下。

       #include <fcntl.h>           /* For O_* constants */
       #include <sys/stat.h>        /* For mode constants */
       #include <semaphore.h>

       sem_t *sem_open(const char *name, int oflag);
       sem_t *sem_open(const char *name, int oflag,
                       mode_t mode, unsigned int value);

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

       int sem_wait(sem_t *sem);

       int sem_trywait(sem_t *sem);

       int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

       int sem_post(sem_t *sem);

       int sem_close(sem_t *sem);

       int sem_unlink(const char *name);

       int sem_destroy(sem_t *sem);

使用POSIX信号量的基本步骤为:

  • 对于命名信号量,用sem_open创建或者打开,并指定名字。对于匿名信号量,应先申请sem_t存储,如果是进程间共享,可放到共享内存中;如果是线程间共享,则可放到全局变量中,然后调用sem_init函数初始化信号量。
  • 调用sem_wait等待信号量。
  • 访问临界区资源。
  • 调用sem_post释放信号量。
  • 对于命名信号量,调用sem_close释放信号量,并可调用sem_unlink删除。对于匿名信号量,调用sem_destroy删除信号量。

下面这个例子演示了命名信号量的使用。这个例子非常简单,父进程创建了两个子进程,然后三个进程都尝试获取信号量。从运行结果不难看出,由于初始值为1,因此刚开始两个进程尝试获取信号量只有1个成功,另一个等待;第一个获取信号量的进程释放后,后续进程才能再获取信号量。

/*
 * posix_sem.c: demostrates POSIX named semaphore
 */
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#define SEM_NAME "/test_posix_shm"
static sem_t *sem = SEM_FAILED;

static void create_sem(void)
{
	sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
	if (sem == SEM_FAILED) {
		perror("sem_open");
		return;
	}
}

static void destroy_sem(void)
{
	if (sem != SEM_FAILED) {
		sem_close(sem);
		sem = SEM_FAILED;
	}
	sem_unlink(SEM_NAME);
}

static pid_t do_child(void)
{
	pid_t pid;
	sigset_t full, old;
	sigfillset(&full);
	sigprocmask(SIG_SETMASK, &full, &old);
	pid = fork();
	if (pid == 0) {
		sigprocmask(SIG_SETMASK, &old, NULL);
		printf("%d: acquiring semaphore\n", getpid());
		sem_wait(sem);
		printf("%d: semaphore acquired\n", getpid());
		sleep(1);
		sem_post(sem);
		printf("%d: semaphore released\n", getpid());
		exit(EXIT_SUCCESS);
	}
	sigprocmask(SIG_SETMASK, &old, NULL);
	return pid;
}

int main(int argc, char *argv[])
{
	create_sem();
	do_child();
	do_child();
	printf("%d: acquiring semaphore\n", getpid());
	sem_wait(sem);
	printf("%d: semaphore acquired\n", getpid());
	sleep(1);
	sem_post(sem);
	printf("%d: semaphore released\n", getpid());
	while (wait(NULL) > 0);
	destroy_sem();
	return 0;
}
gcc -o posix_sem posix_sem.c -lpthread
./posix_sem
30690: acquiring semaphore
30691: acquiring semaphore
30690: semaphore acquired
30692: acquiring semaphore
30690: semaphore released
30691: semaphore acquired
30691: semaphore released
30692: semaphore acquired
30692: semaphore released

下面这个例子与前面的例子结构类似,演示放在共享内存里的匿名信号量的使用。

/*
 * posix_anonsem.c: demostrates POSIX anonymous semaphore
 */
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static char self_exe[MAXPATHLEN + 1];

#define SHM_NAME "/test_posix_anon_sem"
struct shared_data {
	sem_t sem;
};
#define SHM_SIZE sizeof(struct shared_data)
static int shm_id = -1;
static struct shared_data *sd = MAP_FAILED;
static sem_t *sem = SEM_FAILED;

static void create_sem(void)
{
	shm_id = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
	if (shm_id < 0) {
		perror("shm_open");
		return;
	}
	ftruncate(shm_id, SHM_SIZE);
	sd = (struct shared_data *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
	if (sd == MAP_FAILED) {
		perror("mmap");
		return;
	}
	sem = &sd->sem;
	sem_init(sem, 1, 1);
}

static void destroy_sem(void)
{
	if (sem != SEM_FAILED) {
		sem_destroy(sem);
		sem = SEM_FAILED;
	}

	if (sd != MAP_FAILED) {
		munmap(sd, SHM_SIZE);
		sd = MAP_FAILED;
	}
	if (shm_id > 0) {
		close(shm_id);
		shm_id = -1;
		shm_unlink(SHM_NAME);
	}
}

static void child_proc(void)
{
	shm_id = shm_open(SHM_NAME, O_RDWR, 0666);
	if (shm_id < 0) {
		perror("shm_open");
		return;
	}
	ftruncate(shm_id, SHM_SIZE);
	sd = (struct shared_data *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
	if (sd == MAP_FAILED) {
		perror("mmap");
		return;
	}
	sem = &sd->sem;

	printf("%d: acquiring semaphore\n", getpid());
	sem_wait(sem);
	printf("%d: semaphore acquired\n", getpid());
	sleep(1);
	sem_post(sem);
	printf("%d: semaphore released\n", getpid());

	munmap(sd, SHM_SIZE);
	close(shm_id);
}

static pid_t do_child(void)
{
	pid_t pid;
	sigset_t full, old;
	sigfillset(&full);
	sigprocmask(SIG_SETMASK, &full, &old);
	pid = fork();
	if (pid == 0) {
		sigprocmask(SIG_SETMASK, &old, NULL);
		execl(self_exe, self_exe, "child", (char *)0);
		exit(-1);
	}
	sigprocmask(SIG_SETMASK, &old, NULL);
	return pid;
}

int main(int argc, char *argv[])
{
	if (argc == 1) {
		realpath(argv[0], self_exe);
		create_sem();
		do_child();
		do_child();
		printf("%d: acquiring semaphore\n", getpid());
		sem_wait(sem);
		printf("%d: semaphore acquired\n", getpid());
		sleep(1);
		sem_post(sem);
		printf("%d: semaphore released\n", getpid());
		while (wait(NULL) > 0);
		destroy_sem();
	} else {
		child_proc();
	}
	return 0;
}
gcc -o posix_anonsem posix_anonsem.c -lpthread -lrt
./posix_anonsem
32471: acquiring semaphore
32471: semaphore acquired
32472: acquiring semaphore
32473: acquiring semaphore
32471: semaphore released
32473: semaphore acquired
32473: semaphore released
32472: semaphore acquired
32472: semaphore released

System V信号量

System V的信号量与POSIX信号量略有不同,它实际上是一个信号量集合。其相关API如下。

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

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

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

       int semtimedop(int semid, struct sembuf *sops, size_t nsops,
                      const struct timespec *timeout);

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

System V信号量的使用步骤如下:

  • 调用semget函数来创建或者打开一个信号量集合。第一个参数为标识信号量集合的key,可通过ftok函数从一个文件路径来生成key。第二个参数指定信号量集合中的信号量个数。如果是创建信号量,那么第三个参数应带标记IPC_CREAT。
  • 调用semctl来设置信号量初值。
  • 调用semop来实现信号量的P操作。
  • 访问临界区资源。
  • 调用semop来实现信号量的V操作。
  • 调用semctl可删除信号量。

信号量对象是在内核中维护的,因此如果一个进程创建了一个信号量,没有调用semctl删除它就退出了,那么它依然在内核中存在。如果内核配置了CONFIG_PROC_FS,则可以在/proc/sysvipc/sem中看到它。

cat /proc/sysvipc/sem
       key      semid perms      nsems   uid   gid  cuid  cgid      otime      ctime
         0          0   600          1     0     0     0     0 1602142859 1602142857
         0      32769   600          1     0     0     0     0 1602114063 1602142861
         0      98306   600          1     0     0     0     0          0 1608105510

下面这个例子演示了System V信号量的使用。与前面的例子稍有不同的时,在这个例子中,创建的第一个子进程在获取信号量后就调用abort()函数异常中止了。但是从运行结果可以看出,这并没有导致后面等待信号量的进程死锁,这就时System V信号量中的SEM_UNDO的作用。从最后该信号量的状态输出也可看出,最终信号量与初值一样,而且没有等待进程。System V信号量的SEM_UNDO这个功能比POSIX信号量更稳健。

/*
 * sysv_sem.c: demostrates System V semaphore
 */
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static char self_exe[MAXPATHLEN + 1];

#define SEM_NAME "/tmp/test_sysv_sem"
static int sem_id = -1;

#define P(S) \
do { \
	struct sembuf sop = {0, -1, SEM_UNDO}; \
	semop(S, &sop, 1); \
} while(0)

#define V(S) \
do { \
	struct sembuf sop = {0, 1, SEM_UNDO}; \
	semop(S, &sop, 1); \
} while(0)

static void create_sem(void)
{
	sem_id = semget(ftok(SEM_NAME, 1), 1, IPC_CREAT | 0666);
	if (sem_id == -1) {
		perror("semget");
		return;
	}
	semctl(sem_id, 0, SETVAL, 1);
}

static void destroy_sem(void)
{
	if (sem_id != -1) {
		semctl(sem_id, 0, IPC_RMID);
		sem_id = -1;
	}
}

static void dump_sem(void)
{
	printf("GETVAL: %d\n", semctl(sem_id, 0, GETVAL));
	printf("GETNCNT: %d\n", semctl(sem_id, 0, GETNCNT));
	printf("GETZCNT: %d\n", semctl(sem_id, 0, GETZCNT));
}

static void child_proc(int test)
{
	sem_id = semget(ftok(SEM_NAME, 1), 1, 0666);
	if (sem_id < 0) {
		perror("semget");
		return;
	}
	printf("%d: acquiring semaphore\n", getpid());
	P(sem_id);
	printf("%d: semaphore acquired\n", getpid());
	if (test) {
		printf("%d: child abort with semaphore acquired, it won't block since SEM_UNDO is set\n", getpid());
		abort();
	}
	sleep(1);
	V(sem_id);
	printf("%d: semaphore released\n", getpid());
}

static pid_t do_child(int test)
{
	pid_t pid;
	sigset_t full, old;
	sigfillset(&full);
	sigprocmask(SIG_SETMASK, &full, &old);
	pid = fork();
	if (pid == 0) {
		sigprocmask(SIG_SETMASK, &old, NULL);
		if (test)
			execl(self_exe, self_exe, "child", "test", (char *)0);
		else
			execl(self_exe, self_exe, "child", (char *)0);
		exit(-1);
	}
	sigprocmask(SIG_SETMASK, &old, NULL);
	return pid;
}

int main(int argc, char *argv[])
{
	if (argc == 1) {
		realpath(argv[0], self_exe);
		create_sem();
		do_child(1);
		do_child(0);
		printf("%d: acquiring semaphore\n", getpid());
		P(sem_id);
		printf("%d: semaphore acquired\n", getpid());
		sleep(1);
		V(sem_id);
		printf("%d: semaphore released\n", getpid());
		while (wait(NULL) > 0);
		dump_sem();
		destroy_sem();
	} else {
		child_proc(argc > 2 ? 1 : 0);
	}
	return 0;
}
gcc -o sysv_sem sysv_sem.c
./sysv_sem
3290: acquiring semaphore
3290: semaphore acquired
3291: acquiring semaphore
3292: acquiring semaphore
3290: semaphore released
3291: semaphore acquired
3291: child abort with semaphore acquired, it won't block since SEM_UNDO is set
3292: semaphore acquired
3292: semaphore released
GETVAL: 1
GETNCNT: 0
GETZCNT: 0

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值