Skip to content

LV020-posix无名信号量

一、POSIX 无名信号量

有名信号量和无名信号量的差异在于创建和销毁的形式上,其他的是一样的。无名信号量只能存在于内存中,所以就要求使用信号量的进程必须能访问信号量所在的这一块内存,所以无名信号量只能应用在同一进程内的线程之间(共享进程的内存)。另外,该信号量的使用需要借助于共享内存,打开或者创建之前需要进行以下步骤:

  • (1)申请key
  • (2)创建共享内存区域;
  • (3)映射内存区域到进程的虚拟地址空间。

1. 初始化无名信号量

1.1 sem_init()

1.1.1 函数说明

linux下可以使用man 3 sem_init命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_init(sem_t *sem, int pshared, unsigned int value);

函数说明】该函数用于初始化一个无名信号量。

函数参数

  • semsem_t类型,需要初始化的无名信号量名称。

  • psharedint类型,表示这个信号量是在进程的线程之间共享,还是在进程之间共享。一般指定为0,表示这个无名信号量只能由初始化这个信号量的进程使用,不能在进程间使用,并且Linux 不支持使用无名信号量实现进程间的同步。

  • valueunsigned int 类型,表示无名信号量的初始值。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767

返回值int类型,成功返回0,失败返回-1,并设置errno表示错误。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t sem_r; /* 定义成全局变量 */
sem_init(&sem_r, 0, 0);

注意事项none

1.1.2 使用实例

暂无。

2. 销毁无名信号量

2.1 sem_destroy()

2.1.1 函数说明

linux下可以使用man 3 sem_destroy命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_destroy(sem_t *sem);

函数说明】该函数用于销毁一个无名信号量。

函数参数

  • semsem_t *类型,需要关闭的信号量的地址。

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t *sem_r;/* 定义成全局变量 */
sem_destroy(sem_r);

注意事项none

2.1.2 使用实例

暂无。

3. 无名信号量的P操作

与有名信号量的P操作一样。

3.1 sem_wait()

3.1.1 函数说明

linux下可以使用man 3 sem_wait命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_wait(sem_t *sem);

函数说明】该函数用于执行信号量的P操作,也就是获取资源。如果信号量的值大于0,则信号量继续递减,函数立即返回;如果信号量当前的值是0,那么调用就会阻塞,直到可以执行递减操作(即信号量的值升到0以上),或者信号处理程序中断调用。

函数参数

  • semsem_t *类型,表示需要等待的信号量。

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t *sem_r;
sem_wait(sem_r);

注意事项none

3.1.2 使用实例

暂无。

3.2 sem_trywait()

3.2.1 函数说明

linux下可以使用man 3 sem_trywait命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_trywait(sem_t *sem);

函数说明】该函数用于执行信号量的P操作。sem_trywait()sem_wait()相同,不同之处是如果不能立即执行递减操作而需要等待的话,调用此函数将返回错误(errno设置为EAGAIN)而不是阻塞。

函数参数

  • semsem_t *类型,表示需要等待的信号量。

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t *sem_r;
sem_trywait(sem_r);

注意事项none

3.2.2 使用实例

暂无。

3.3 sem_trywait()

3.3.1 函数说明

linux下可以使用man 3 sem_trywait命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

函数说明】该函数用于执行信号量的P操作。sem_timedwait()sem_wait()相同,不同的是abs_timeout指定了如果不能立即执行递减操作而需要等待的时间,超时将会返回。

函数参数

  • semsem_t *类型,表示需要等待的信号量。
  • abs_timeoutstruct timespec类型的结构体指针变量,它指向一个时间结构体,该时间结构体指定了自Epoch (1970-01-01 00:00:00 +0000 (UTC))以来的以秒和纳秒为单位的绝对超时。struct timespec 结构体成员如下:
c
struct timespec 
{
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t *sem_r;
struct timespec tout;
sem_timedwait(sem_r, &tout);

注意事项】如果可以立即执行操作,那么sem_timedwait()永远不会出现超时错误,而不管abs_timeout的值是多少。

3.3.2 使用实例

暂无。

4. 无名信号量的V操作

与有名信号量的V操作一样。

4.1 sem_post()

4.1.1 函数说明

linux下可以使用man 3 sem_post命令查看该函数的帮助手册。

c
/* Link with -pthread. */
/* 需包含的头文件 */
#include <semaphore.h>

/* 函数声明 */
int sem_post(sem_t *sem);

函数说明】该函数用于执行信号量的V操作,也就是获取资源。

函数参数

  • semsem_t *类型,表示需释放的信号量。

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <semaphore.h>

/* 至少应该有的语句 */
sem_t *sem_r;
sem_post(sem_r);

注意事项none

4.1.2 使用实例

暂无。

二、使用实例

1. 实例源码

c
/* 头文件 */
#include <stdio.h>   /* perror fgets shmat*/
#include <sys/shm.h> /* shmget shmat*/
#include <sys/ipc.h> /* shmget ftok */
#include <string.h>  /* strcpy */
#include <stdlib.h>  /* system */
#include <signal.h>  /* sigaction sigemptyset */


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


sem_t sem_r, sem_w; /* 定义两个信号量 */
char *shmAddr;      /* 共享内存首地址 */

void *readSem(void *arg);   /* 读取数据线程 */
void deleteSemfile(int sig);/* 删除无名信号量 */

/* 主函数 */
int main(int argc, char *argv[])
{
	key_t key;
	int shmid;
	struct sigaction act;/* 处理信号行为的结构体变量 */
	pthread_t tid;
	/* 1. 生成key */
	key = ftok("./key.txt", 100);
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	printf("key=%x\n", key);
	/* 2. 创建或者打开共享内存 */
	shmid = shmget(key, 512, IPC_CREAT|0666);
	if(shmid < 0)
	{
		perror("shmget");
		return 0;
	}
	printf("shmid=%d\n", shmid);
	/* 3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问*/
	shmAddr = shmat(shmid, NULL, 0);
	if(shmAddr < 0)
	{
		perror("shmat");
		return 0;
	}
	printf("shmAddr=%p\n", shmAddr);
	/* 4. 信号捕获 */
	act.sa_handler = deleteSemfile;  /* 指定信号处理函数 */
	act.sa_flags = 0;                /* sa_flags指定了一组标志,这些标志用于控制信号的处理过程 */
	sigemptyset(&act.sa_mask);       /* 将信号集初始化为空 */
	sigaction(SIGINT, &act, NULL);   /* 捕捉信号 */
	/* 5. 信号量初始化) */
	sem_init(&sem_r, 0, 0);
	sem_init(&sem_w, 0, 1);
	/* 6. 创建线程 */
	pthread_create(&tid, NULL, readSem, NULL);
	/* 7. 写入数据 */
	while(1)
	{
		sem_wait(&sem_w);/* 获取资源 */
		printf(">");
        fgets(shmAddr, 500, stdin);
		sem_post(&sem_r);/* 释放资源 */
	}

	return 0;
}

/* 线程函数 */
void *readSem(void *arg)
{
	while(1)
	{
		sem_wait(&sem_r);
		printf("%s\n",shmAddr);
		sem_post(&sem_w);

	}

}

/* 删除信号量 */
void deleteSemfile(int sig)
{
	sem_destroy(&sem_r);
	sem_destroy(&sem_w);
	printf("Destroy sem_r and sem_w!\n");
	exit(0);
}

2. 测试效果

在终端执行以下命令编译程序:

shell
gcc test.c -Wall -lpthread # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
key=643d0b38
shmid=18
shmAddr=0x7f421bd53000
>qwert
qwert

>^CDestroy sem_r and sem_w!