《UNIX网络编程 卷2》 笔记: 使用FIFO实现信号量

来源:互联网 发布:辐射4女角色捏脸数据 编辑:程序博客网 时间:2024/06/04 23:32

上节我们给出的例子使用的都是基于内存的信号量,还有一种信号量是有名信号量。两者的关系就像管道FIFO一样。有趣的是,有名信号量可以使用FIFO来实现。具体思想是:每个有名信号量和一个FIFO关联,FIFO中数据的字节数代表该信号量的值。sem_post函数往该FIFO中写入一个字节,sem_wait函数从FIFO中读出一个字节。

我们自己实现的信号量类型mysem_t定义如下:

typedef struct {/*FIFO读和写描述符*/int sem_fd[2];int sem_magic;} mysem_t;#define SEM_MAGIC0x89674532#define MYSEM_FAILED ((mysem_t *)(-1))

它包含FIFO读写端描述符和一个表示信号量的魔数。

创建或打开一个有名信号量的实现函数如下:

mysem_t* mysem_open(const char *pathname, int oflag, ...){int i, flags, save_errno;char c;va_list ap;mode_t mode;unsigned int value;mysem_t *sem;if (oflag & O_CREAT) { /*如果指定O_CREAT标志,文件不存在则会创建文件*/va_start(ap, oflag);mode = va_arg(ap, mode_t);value = va_arg(ap, unsigned int);va_end(ap);/*创建FIFO*/if (mkfifo(pathname, mode) < 0) {/*如果返回EEXIST错误,但没有指定O_EXCL标志,忽略该错误*/if (errno == EEXIST && (oflag & O_EXCL) == 0)oflag &= ~O_CREAT;elsereturn MYSEM_FAILED;}}/*分配内存*/if ((sem = malloc(sizeof(mysem_t))) == NULL)return MYSEM_FAILED;sem->sem_fd[0] = sem->sem_fd[1] = -1;/*以非阻塞读的方式打开FIFO*/if ((sem->sem_fd[0] = open(pathname, O_RDONLY | O_NONBLOCK)) < 0)goto error;/*以非阻塞写的方式打开FIFO*/if ((sem->sem_fd[1] = open(pathname, O_WRONLY | O_NONBLOCK)) < 0)goto error;/*将FIFO读描述符改为阻塞*/if ((flags = fcntl(sem->sem_fd[0], F_GETFL, 0)) < 0)goto error;flags &= ~O_NONBLOCK;if (fcntl(sem->sem_fd[0], F_SETFL, flags) < 0)goto error;if (oflag & O_CREAT) {/*写入value字节数据*/for (i = 0; i < value; i++)if (write(sem->sem_fd[1], &c, 1) != 1)goto error;}sem->sem_magic = SEM_MAGIC;return sem;error:/*保存出错时的errno*/save_errno = errno;if (oflag & O_CREAT)unlink(pathname);close(sem->sem_fd[0]);close(sem->sem_fd[1]);free(sem);errno = save_errno;return MYSEM_FAILED;}/*包裹函数*/mysem_t* MySem_open(const char *pathname, int oflag, ...){    va_list ap;    mode_t mode;    unsigned int value;    mysem_t *sem;        va_start(ap, oflag);    mode = va_arg(ap, mode_t);    value = va_arg(ap, unsigned int);    va_end(ap);        if ((sem = mysem_open(pathname, oflag, mode, value)) == MYSEM_FAILED)        err_sys("mysem_open error");    return sem;}

每次打开一个有名信号量,我们打开它关联的FIFO两次,一次是为了读,一次是为了写。第一次我们使用非阻塞读的方式打开该FIFO是为了避免进程被阻塞(详见FIFO这一节 图4-21),后面我们又调用fcntl将打开的读描述符设置为阻塞的方式。

出错处理时我们先保存errno的值,因为后面的系统调用可能改变它的值。

关闭有名信号量和删除有名信号量的实现函数如下:

int mysem_close(mysem_t *sem){if (sem->sem_magic != SEM_MAGIC) {errno = EINVAL;return -1;}sem->sem_magic = 0;/*关闭FIFO*/if (close(sem->sem_fd[0]) == -1 || close(sem->sem_fd[1]) == -1) {free(sem);return -1;}free(sem);return 0;}int mysem_unlink(const char *pathname){/*删除FIFO*/return unlink(pathname);}

挂起一个有名信号量和等待一个有名信号量的实现函数如下:

int mysem_post(mysem_t *sem){char c;if (sem->sem_magic != SEM_MAGIC) {errno = EINVAL;return -1;}if (write(sem->sem_fd[1], &c, 1) == 1)return 0;return -1;}int mysem_wait(mysem_t *sem){char c;if (sem->sem_magic != SEM_MAGIC) {errno = EINVAL;return -1;}if (read(sem->sem_fd[0], &c, 1) == 1)return 0;return -1;}
mysem_post函数往FIFO中写入1字节数据,给信号量的值加1。它不会阻塞。

mysem_wait函数从FIFO中读出1字节数据,给信号量的值减1。当信号量的值为0(FIFO中没有数据)时,它就会阻塞直到信号量的值大于0(FIFO中有数据)。

原创粉丝点击