虚拟设备文件
Linux 内核自 2.6.22 版本开始逐步增加了三个虚拟设备文件:eventfd、timerfd、signalfd。这三个文件让应用程序可以通过标准 I/O 操作的方式代替传统调用 API 的方式来使用事件(信号量)、定时器和信号资源,这带来的最大好处是应用程序可以通过使用 select(或 poll、epoll)同时监听多个此类文件(或此类文件与其他文件),将对多个事件的异步并行处理方便地转化为同步串行处理,这在许多应用中非常有用。SylixOS 完全兼容这三个虚拟设备文件,并且增加了一个 hstimerfd,用于高精度定时器。
下面将分别介绍它们的使用方法。
eventfd
eventfd 主要用于线程之间的事件通知,Linux 由于支持 fork 系统调用,因此也可以用于父子进程之间的事件通知。相关 API 介绍如下:
int eventfd(unsigned int initval, int flags);
int eventfd_read(int fd, eventfd_t *value);
int eventfd_write(int fd, eventfd_t value);
函数 eventfd 原型分析:
- 该函数成功返回一个文件描述符,失败返回负数并设置错误号。
- 参数 initval 表示事件的初始状态,例如为 0 表示当前没有任何事件产生。
- 参数 flags 为该事件文件的操作选项,可以为 EFD_CLOEXEC、EFD_NONBLOCK、和 EFD_SEMAPHORE。EFD_SEMAPHORE 的具体含义随后讲解。
函数 eventfd_read 用于读取事件文件,即等待事件的发送。当没有事件且未使用 EFD_NONBLOCK 参数,该函数将会阻塞,其原型分析如下:
- 该函数成功返回 0,失败返回错误码。
- 参数 fd 即为通过 eventfd 打开的一个事件文件描述符。
- 输出参数 value 保存读取的事件数量值,其行为与 EFD_SEMAPORE 标志有关。eventfd_t 在大多数系统中被定义为一个无符号 64 位整型数,正常情况下,它能表示的事件数量可以理解为无限多。
当使用 eventfd 函数的参数 flags 包含了 EFD_SEMAPHORE 时,其含义为将事件按照计数信号量来操作。即如果当前已经产生了多个事件,每一次读取只会获得一个事件,该文件内部的事件计数器只减一, value 里面的内容为 1。当没有使用 EFD_SEMAPHORE 时,一次读取所有的事件,value 里面的内容即为读取的事件数量,内部的事件计数器归零。两种方式可满足应用程序在不同场景的需求。
函数 eventfd_write 用于写事件文件,即发送事件。当内部事件计数器达到最大值时,将不能继续发送事件,此时返回错误,其原型分析如下:
- 该函数成功返回 0,失败返回错误码。
- 参数 fd 即为通过 eventfd 打开的一个事件文件描述符。
- 输入参数 value 为发送的事件数量。
下面的例子展示了 eventfd 的使用方法。
#include <stdio.h>
#include <sys/eventfd.h>
#include <pthread.h>
void *event_write_routine (void *arg)
{
eventfd_t value = 1;
int event_fd = (int)arg;
int ret;
while (1) {
ret = eventfd_write(event_fd, value);
if (ret) {
fprintf(stderr, "write eventfd error.\n");
break;
}
value++;
}
return (NULL);
}
int event_write_server_start (int event_fd, void *(*routine)(void *))
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, routine, (void *)event_fd);
if (ret != 0) {
fprintf(stderr, "pthread create failed.\n");
return (-1);
}
return (0);
}
int main (int argc, char *arvg[])
{
int event_fd;
int ret;
eventfd_t value;
event_fd = eventfd(0, 0);
if (event_fd < 0) {
fprintf(stderr, "open eventfd failed.\n");
return (-1);
}
ret = event_write_server_start(event_fd, event_write_routine);
if (ret) {
fprintf(stderr, "start eventfd write server failed.\n");
close(event_fd);
return (-1);
}
while (1) {
ret = eventfd_read(event_fd, &value);
if (ret) {
fprintf(stderr, "read eventfd error.\n");
break;
}
fprintf(stdout, "read event value count: %llu.\n", value);
}
close(event_fd);
return (0);
}
在上面的程序中,子线程不断地往事件文件写入数值递增的事件,主线程不断地读取事件并打印本次事件的数值。其运行结果如下:
# ./event_Synchronization_between_threads
read event value count: 1.
read event value count: 2.
read event value count: 3.
read event value count: 4.
read event value count: 5.
read event value count: 6.
read event value count: 7.
read event value count: 8.
上面的结果,可以看出,每一次读取的事件数值反映的是写入的事件数值。实际上,在未设置 EFD_SEMAPHORE 标志的情况下,eventfd_read 函数总是一次读取所有事件(无论eventfd_write执行了多少次)并将内部事件值清零。一次事件的数值可理解为事件发生后所能处理的资源数量;EFD_SEMAPHORE 则使事件接收方每次只能读取一个事件,当存在多个事件的情况下可连续读取。
timerfd
在“时间管理”章节,我们曾介绍了使用 SylixOS 定时器相关的 API 函数,使用 timerfd 也同样能够实现定时器功能,相关 API 如下。
#include <timerfd.h>
int timerfd_create(clockid_t clockid, int flags);
int timerfd_settime(int fd, int flags, const struct itimerspec *ntmr,
struct itimerspec *otmr);
int timerfd_gettime(int fd, struct itimerspec *currvalue);
函数 timerfd_create 原型分析:
- 该函数成功返回定时器文件描述符,失败返回负数并设置错误号。
- 参数 clockid 表示该定时器参考的时钟源类型,可以为 CLOCK_REALTIME 或 CLOCK_MONOTONIC,分别代表真实时间和线性递增时间。
- 参数 flags 是定时器文件选项位标识,可以是 TFD_CLOEXEC(等于 O_CLOEXEC)和 TFD_NONBLOCK(等于 O_NONBLOCK)。
函数 timerfd_settime 用于设置定时器启动时间以及重载时间间隔。原型分析如下:
- 该函数成功返回 0,失败返回负数并设置错误号。
- 参数 fd 为定时器文件描述符。
- 参数 flags 为与定时时间相关的标识,可以是 0 或者 TIMER_ABSTIME,它影响参数 ntmr 的含义。
- 参数 ntmr 描述了定时器的时间参数:启动时间和重载时间。
- 输出参数 otmr 保存旧的时间参数。可以为 NULL。
数据类型 itimerspec 定义如下:
struct itimerspec {
struct timespec it_interval; /* 定时器重载值 */
struct timespec it_value; /* 到下一次到期为止剩余时间 */
};
it_interval 为定时器周期触发的时间间隔,it_value 表示定时器第一次触发的时间。该值的含义由 flags 定义,如果设置了 TIMER_ABSTIME 标识,则表示 it_value 为一个绝对时间,当系统时间到达该值时定时器触发。如果 flags 为 0,则表示 it_value 为一个相对时间,经过该时间值后定时器触发。两者的类型都为 timespec,这意味着,定时器可以达到纳秒级别的时间精度。
函数 timerfd_gettime 获得定时器文件当前的时间参数,其原型分析如下:
- 该函数成功返回 0,失败返回负数并设置错误号。
- 参数 fd 为定时器文件描述符。
- 输出参数 currvalue 保存当前时间参数。
一旦调用 timerfd_settime 成功,即会启动定时器,并在满足设置的时间条件时触发定时器。应用程序可使用 read 或 select 来等待定时器触发(与 GPIO 设备相同)。
程序清单 timerfd应用示例(Application_of_Timerfd)
#include <sys/timerfd.h>
#include <stdio.h>
#include <stdint.h>
#include <time.h>
static int show_elapsed_time (void)
{
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs;
int nsecs;
if (first_call) {
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
return (-1);
}
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1) {
return (-1);
}
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0) {
secs--;
nsecs += 1000000000;
}
fprintf(stdout, "time elapsed: %d.%03d seconds.\n",
secs, (nsecs + 500000) / 1000000);
return (0);
}
int main(int argc, char *argv[])
{
struct itimerspec time;
int timer_fd;
int ret;
uint64_t expired;
ssize_t read_len;
timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (timer_fd < 0) {
fprintf(stderr, "create timerfd failed.\n");
return (-1);
}
time.it_value.tv_sec = 3;
time.it_value.tv_nsec = 0;
time.it_interval.tv_sec = 1;
time.it_interval.tv_nsec = 0;
ret = timerfd_settime(timer_fd, 0, &time, NULL);
if (ret) {
fprintf(stderr, "start timerfd error.\n");
close(timer_fd);
return (-1);
}
ret = show_elapsed_time();
if (ret) {
close(timer_fd);
return (-1);
}
while (1) {
read_len = read(timer_fd, &expired, sizeof(uint64_t));
if (read_len < sizeof(uint64_t)) {
fprintf(stderr, "read timerfd error.\n");
break;
}
ret = show_elapsed_time();
if (ret) {
break;
}
fprintf(stdout, "timer is triggered, expire count = %llu.\n", expired);
sleep(2);
}
close(timer_fd);
return (0);
}
上面的程序中,创建定时器时,其参数 flags 为 0,表示使用相对时间,在调用 timerfd_settime 时,其时间参数的设置表示,定时器自启动后经过 3 秒触发,之后每隔 1 秒触发一次。私有函数 show_elapsed_time 用于显示自定时器启动后所经过的时间,以此作为检验定时器精确度的一个标准。程序在等待定时器第一次触发后,每隔 2 秒周期性地去等待定时器的触发状态。
注意,这里通过 read 的方式等待定时器触发,其读取的数据为一个 64 位无符号类型,该数据表示的是定时器到本次触发为止总共触发了多少次,我们也把它叫做定时器到期的次数。
程序运行后,结果如下:
# ./Application_of_Timerfd
time elapsed: 0.000 seconds.
time elapsed: 2.996 seconds.
timer is triggered, expire count = 1.
time elapsed: 4.996 seconds.
timer is triggered, expire count = 2.
time elapsed: 6.996 seconds.
timer is triggered, expire count = 2.
time elapsed: 8.996 seconds.
timer is triggered, expire count = 2.
time elapsed: 10.996 seconds.
timer is triggered, expire count = 2.
time elapsed: 12.996 seconds.
timer is triggered, expire count = 2.
......
从结果可以看出,定时器在第一次触发时,经过了 2.996 秒(有一定精度偏差),并且显示定时器到期计数为 1,这符合我们的设定。在之后,由于定时器每隔 1 秒触发一次,因此我们每隔 2 秒去获得的定时器到期计数为 2,也与预期一致。
需要注意的是,设定定时器时间参数时,如果 it_value 的时间值为 0,并不表示定时器立即触发,而是表示停止定时器。同样的,it_interval 的时间值为 0,也不表示定时器无等待时间无限触发,而是停止定时器。
hstimerfd
SylixOS 支持时间精度可高于系统时钟的定时器,并提供相关 API 函数(见“时间管理”章节),高精度定时器只能保证其时间精度不低于普通定时器,这完全取决于系统硬件以及 BSP 包的支持。SylixOS 同样提供了类似于 timrfd 的 hstimerfd 文件,让应用程序通过标准 I/O 使用高精度定时器。相关 API 定义如下:
#include <sys/hstimerfd.h>
int hstimerfd_hz(void);
int hstimerfd_create(int flags);
int hstimerfd_settime(int fd,
const struct itimerspec *ntmr,
struct itimerspec *otmr);
int hstimerfd_settime2(int fd, hstimer_cnt_t *ncnt, hstimer_cnt_t *ocnt);
int hstimerfd_gettime(int fd, struct itimerspec *currvalue);
int hstimerfd_gettime2(int fd, hstimer_cnt_t *currvalue);
hstimerfd_settime 与 hstimerfd_gettime 与上一节的普通定时器 API 的参数和行为一致。hstimerfd_hz 函数返回高精度定时器的计数频率,亦是可以达到的定时精度。
函数 hstimerfd_create 原型分析:
- 该函数成功时返回文件描述符,失败时返回负数并设置错误号。
- 参数 flags 为文件标识,可以是 HSTFD_CLOEXEC(等于 O_CLOEXEC)和 HSTFD_NONBLOCK(等于 O_NONBLOCK)。
函数 hstimerfd_settime2 原型分析:
- 该函数成功返回 0,失败返回错误码。
- 参数 fd 为高精度定时器文件描述符。
- 参数 ncnt 类似为 hstimer_cnt_t,表示新的定时器时间计数参数。
- 输出参数 ocnt 保存旧的定时器时间计数参数。
hstimerfd 提供了新的时间参数方式,即使用高精度定时器的计数值来作为其时间参数,数据结构 hstimer_cnt_t 定义如下:
typedef struct hstimer_cnt {
unsigned long value;
unsigned long interval;
} hstimer_cnt_t;
成员变量 value 表示定时器自启动后第一次触发经过的计数值(即首次到期计数值),interval 为定时器周期触发的计数值。
函数 hstimerfd_gettime2 原型分析:
- 该函数成功返回 0,失败返回错误码。
- 参数 fd 为高精度定时器文件描述符。
- 输出参数 currvalue 保存定时器当前的时间参数。
高精度定时器除了时间精度与普通定时器有区别以外,其他行为与普通定时器相似,因此这里不再给出相关示例。读者可根据程序清单 程序清单 timerfd应用示例(Application_of_Timerfd)的程序稍作修改,即可使用高精度定时器。
signalfd
传统的信号处理方式是使用 signal 或 sigaction 函数注册关心的信号处理函数,在信号发生时,这些函数会以异步的方式被调用,因此在使用中需要考虑数据并发的问题,其相关 API 及其使用方法见“信号系统”章节。signalfd 允许应用程序以文件的方式等待信号的产生并同步处理这些信号,signalfd 为一个标准 I/O 设备文件,其定义位于 <sys/signalfd.h> 头文件,如下所示:
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);
函数 signalfd 原型分析:
- 该函数成功返回一个信号文件描述符,失败返回负数并设置错误号。
- 参数 fd 表示一个已有的信号文件描述符。如果为-1 则表示新创建一个信号文件;如果 fd 为一个已有的信号文件描述符,则表示重新设置其需要处理的信号。
- 参数 mask 为一个包含了需要关心的信号集。
- 参数 flags 可以为 SFD_CLOEXEC(等于 O_CLOEXEC)和 SFD_NONBLOCK(等于 O_NONBLOCK)。
当成功调用 signalfd 函数后,其返回的文件描述符即与参数 mask 所指定的信号进行了关联。之后使用 read 函数等待信号的发生,读取的数据为一个 signalfd_siginfo 结构,其定义如下:
struct signalfd_siginfo {
uint32_t ssi_signo; /* Signal number */
int32_t ssi_errno; /* Error number (unused) */
int32_t ssi_code; /* Signal code */
uint32_t ssi_pid; /* PID of sender */
uint32_t ssi_uid; /* Real UID of sender */
int32_t ssi_fd; /* File descriptor (SIGIO) */
uint32_t ssi_tid; /* Kernel timer ID (POSIX timers) */
uint32_t ssi_band; /* Band event (SIGIO) */
uint32_t ssi_overrun; /* POSIX timer overrun count */
uint32_t ssi_trapno; /* Trap number that caused signal */
int32_t ssi_status; /* Exit status or signal (SIGCHLD) */
int32_t ssi_int; /* Integer sent by sigqueue(3) */
uint64_t ssi_ptr; /* Pointer sent by sigqueue(3) */
uint64_t ssi_utime; /* User CPU time consumed (SIGCHLD) */
uint64_t ssi_stime; /* System CPU time consumed (SIGCHLD) */
uint64_t ssi_addr; /* Address that generated signal */
/* (for hardware-generated signals) */
uint8_t pad[48];
};
signalfd_siginfo 的成员变量 ssi_signo 为当前信号的编号,我们可以根据它对不同的信号作对应的处理。signalfd_siginfo 与 siginfo_t 的很多同名成员的含义相同,关于 siginfo_t 的详细介绍见“信号系统”章节。
下面以一个简单的例子展示 signalfd 的使用方法。
#include <sys/signalfd.h>
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
sigset_t sig_mask;
int sig_fd;
struct signalfd_siginfo sig_info;
ssize_t read_size;
sigemptyset(&sig_mask);
sigaddset(&sig_mask, SIGINT);
sigaddset(&sig_mask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &sig_mask, NULL) == -1) {
fprintf(stderr,"sigprocmask error.\n");
return (-1);
}
sig_fd = signalfd(-1, &sig_mask, 0);
if (sig_fd < 0) {
fprintf(stderr, "crteate signalfd error.\n");
return (-1);
}
while (1) {
read_size = read(sig_fd, &sig_info, sizeof(struct signalfd_siginfo));
if (read_size != sizeof(struct signalfd_siginfo)) {
fprintf(stderr, "read signalfd error.\n");
break;
}
if (sig_info.ssi_signo == SIGINT) {
printf("got SIGINT signal.\n");
} else if (sig_info.ssi_signo == SIGQUIT) {
printf("got SIGQUIT signal.\n");
break;
} else {
fprintf(stderr, "got unexpected signal.\n");
}
}
close(sig_fd);
return (0);
}
上面的程序中,我们将关心的两个信号 SIGINT 和 SIGQUIT 加入信号集中,随后通过 sigprocmask 将这两个信号阻塞。这里和使用 signal 或 sigaction 处理信号的方式不同,它们要求关心的信号不能被阻塞,而使用 signalfd 时,如果不阻塞这些信号,则当信号发生时会转到默认的信号处理函数中。
当信号发生时,虽然这些信号被阻塞了,但系统会将与这些信号关联的文件置为可读状态,因此我们使用 read 函数等待信号的发生。使用这种方式,SIGINT 和 SIGQUIT 在一个线程里被串行处理。该程序在运行后,首先通过 ps 命令查看其结果如下:
# ./Application_of_Signalfd &
# ps
NAME FATHER STAT PID GRP MEMORY UID GID USER
----------------------- --------------- ---- ----- ----- ---------- ----- ----- ------
kernel <orphan> R 0 0 16KB 0 0 root
Application_of_Signalfd <orphan> R 124 124 232KB 0 0 root
测试程序的名称为 sigfd_test,其 PID 为 124(PID 的值根据实际情况会发生变化),下面我们使用 kill 命令向该进程发送关心的两个信号,SIGINT 对应的编号为 2,SIGQUIT 对应的编号为 3,结果如下:
# kill -n 2 124
got SIGINT signal.
# kill -n 2 124
got SIGINT signal.
在 Linux 下,我们使用 Ctrl+C 组合键即可给当前进程发送 SIGINT 信号,使用 Ctrl+\ 组合键即可对当前进程发送 SIGQUIT 信号。在 SylixOS 中,使用 Ctrl+C 总是使当前进程退出,而不会输出上面的信息。
bmsgfd
bmsgfd设备用于线程间通信功能,由于 bmsgfd 是文件系统中存在的文件,因此也可以用于不同进程之间的通信,通过 bmsgfd 设备在两个进程中实现消息传递,相关 API 定义如下:
#include <sys/bmsgfd.h>
int bmsgfd(const char *name, int flags, ...);
函数 bmsgfd 原型分析:
- 此函数返回大于0的文件描述符,失败返回-1。
- 参数 name 是 msg 名字。
- 参数 flags 是文件打开的标志。
- 参数 ... 是可变参数列表。
#include <sys/bmsgfd.h>
int bmsgfd_bind(int fd, const char *name);
函数 bmsgfd_bind 原型分析:
- 此函数返回错误号。
- 参数 fd 是文件描述符。
- 参数 name 是 msg 名字。
#include <sys/bmsgfd.h>
int bmsgfd_unbind(int fd);
函数 bmsgfd_unbind 原型分析:
- 此函数返回错误号。
- 参数 fd 是文件描述符。
#include <sys/bmsgfd.h>
int bmsgfd_timeout(int fd, unsigned long *send_ms, unsigned long *recv_ms);
函数 bmsgfd_timeout 原型分析:
- 此函数返回错误号。
- 参数 fd 是文件描述符。
- 参数 send_ms 是发送超时时间。
- 参数 recv_ms 是接收超时时间。
bmsgfd 函数用于打开一个 bmsg 文件,如果指定的 flags 值包含 O_CREAT 和 BMSGFD_SETBUFFER,则需要指定第三个参数的模式值(该值为8进制类型值,例如0666)和第四个参数 struct bmsg_param。
struct bmsg_param 结构体定义
struct bmsg_param {
UINT32 param_flags; /* FIOBMSGSET set options */
UINT32 total_size; /* total buffer size */
UINT32 atomic_size; /* max atomic size in buffer */
UINT32 auto_unlink; /* unlink on last close */
UINT32 reserved[4];
};
- param_flags:FIOBMSGSET 选项,是否强制设置缓冲区参数。
- total_size:总缓冲区大小。
- atomic_size:缓冲区中单个消息块的最大占用空间大小。
- auto_unlink:bmsg 关闭的时候是否自动删除文件。
下面程序展示了两个进程通过 bmsg 来通信的场景。
进程1:打开进程2创建的 bmsg 文件,写满 bmsg 缓冲区并通知进程2。
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <sys/bmsgfd.h>
#define BMSG_NAME "bmsg1"
#define WRITE_DATA "abcdefghijklmnopqrstuvwxyz"
#define SEM_FILE "sem_named"
int main (int argc, char **argv)
{
int fd, ret;
sem_t *sem;
ssize_t ssret;
unsigned long send_timeout = 1000;
sem = sem_open(SEM_FILE, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
return (-1);
}
fd = bmsgfd(BMSG_NAME, O_RDWR);
if (fd < 0) {
perror("bmsgfd");
return (-1);
}
ret = bmsgfd_timeout(fd, &send_timeout, NULL);
if (ret < 0) {
perror("bmsgfd_timeout");
return(-1);
}
while (1) {
ssret = write(fd, WRITE_DATA, strlen(WRITE_DATA));
if (ssret != strlen(WRITE_DATA)) {
if (ssret < 0) {
perror("write");
return (-1);
}
break;
}
}
printf("write finish\n");
sem_post(sem);
sem_wait(sem);
sem_close(sem);
sem_unlink(SEM_FILE);
return (0);
}
进程2:创建 bmsg 文件,等待进程1写满 bmsg 缓冲区后开始读取缓冲区消息。
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <sys/bmsgfd.h>
#define BMSG_READ_SIZE 64
#define BMSG_NAME "bmsg1"
#define WRITE_DATA "abcdefghijklmnopqrstuvwxyz"
#define SEM_FILE "sem_named"
int main (int argc, char **argv)
{
int fd, ret;
char rbuf[BMSG_READ_SIZE] = {0};
sem_t *sem;
ssize_t ssret;
unsigned long recv_timeout = 1000;
struct bmsg_param bp;
bp.param_flags = 1;
bp.total_size = 128;
bp.atomic_size = strlen(WRITE_DATA);
bp.auto_unlink = 1;
sem_unlink(SEM_FILE);
sem = sem_open(SEM_FILE, O_CREAT, 0644, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
return (-1);
}
fd = bmsgfd(BMSG_NAME, O_CREAT | BMSGFD_SETBUFFER | O_RDWR, 0666, &bp);
if (fd < 0) {
perror("bmsgfd");
return(-1);
}
ret = bmsgfd_timeout(fd, NULL, &recv_timeout);
if (ret < 0) {
perror("bmsgfd_timeout");
return(-1);
}
sem_wait(sem);
while (1) {
bzero(rbuf, sizeof(rbuf));
ssret = read(fd, rbuf, sizeof(rbuf));
if (ssret != strlen(WRITE_DATA)) {
if (ssret < 0) {
perror("read");
return (-1);
}
break;
}
printf("recv: %s\n", rbuf);
}
printf("read finish\n");
sem_post(sem);
sem_close(sem);
return (0);
}
在 SylixOS shell 下运行程序:
# ./process2 &
# ./process1
write finish
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
read finish
从运行结果看,进程1写满了 bmsg 缓冲区,进程2收到信号后开始读取缓冲区,继而打印:
write finish
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
recv: abcdefghijklmnopqrstuvwxyz
read finish
semfd
semfd 设备实现了标准信号量的功能,可以用于两个不同线程之间通信,由于 semfd 是文件系统中存在的文件,因此也可以用于不同进程之间的通信,通过 semfd 设备可以在两个进程之间实现信号量的 PEND 和 POST 操作,SylixOS 针对 semfd 设备提供了两个标准接口供应用程序使用。
#include <sys/semfd.h>
int semfd(const char *name, int flags, ...);
函数 semfd 原型分析:
- 此函数成功返回大于0的文件描述符,失败返回-1。
- 参数 name 是 semfd 文件名字。
- 参数 flags 是文件打开的标志。
- 参数 ... 是可变参数列表。
#include <sys/semfd.h>
int semfd_timeout (int fd, unsigned long *ms);
函数 semfd_timeout 原型分析:
- 此函数成功返回0,失败返回-1。
- 参数 fd 是文件描述符。
- 参数 ms 是信号量超时值。
semfd 函数用于打开一个信号量设备,如果指定的 flags 值包含 O_CREAT,则需要指定第三个参数的模式值(该值为8进制类型值,例如0666)和第四个参数 struct semfd_param。
struct semfd_param 结构体定义
struct semfd_param {
semfd_type sem_type; /* Type */
UINT32 sem_opts; /* Options */
UINT32 sem_value; /* Initial value */
UINT32 sem_max; /* Max value */
UINT32 auto_unlink; /* unlink on last close */
UINT32 reserved[3];
};
- sem_type:信号量类型包括:
- SEMFD_TYPE_BINARY(二值信号量)。
- SEMFD_TYPE_COUNTING(计数信号量)。
- SEMFD_TYPE_MUTEX(互斥信号量)。
- sem_opts:信号量选项,与内核信号量选项一致。
- sem_value:信号量的初始化值,范围 [0, 2147483647]。
- sem_max:信号量的最大值,范围[0, 2147483647]。
- auto_unlink:信号量关闭的时候是否自动删除文件。
注意:name 指定的文件将被自动创建到 /dev/semfd 目录下。
semfd_timeout 函数用于设置信号量的超时值,单位为毫秒,特殊地,如果超时时间被指定为 unsigned long 最大值,信号量的超时值将被设置为永远等待。
下面程序展示了两个进程通过 semfd 来操作二值信号量的场景。
进程1:创建二值信号量类型的 semfd。
#include <stdio.h>
#include <sys/semfd.h>
int main (int argc, char **argv)
{
int fd;
int semval;
struct semfd_param sfp;
unsigned long tmo;
sfp.auto_unlink = 1;
sfp.sem_max = 1;
sfp.sem_value = 0;
sfp.sem_type = SEMFD_TYPE_BINARY;
sfp.sem_opts = 0;
fd = semfd("test", O_CREAT | O_RDWR, 0666, &sfp);
if (fd < 0) {
perror("semfd");
return (-1);
}
tmo = ULONG_MAX;
semfd_timeout(fd, &tmo);
read(fd, &semval, sizeof(int));
printf("semfd read value: %d\n", semval);
close(fd);
return (0);
}
进程2:打开进程1创建的 semfd。
#include <stdio.h>
#include <sys/semfd.h>
int main (int argc, char **argv)
{
int fd;
int semval;
fd = semfd("test", O_RDWR);
if (fd < 0) {
perror("semfd");
return (-1);
}
semval = 1;
printf("semfd write value: %d\n", semval);
write(fd, &semval, sizeof(int));
return (0);
}
在 SylixOS shell 下运行程序:
# ./process1 &
# ./process2
semfd write value: 1
semfd read value: 1
从运行结果看进程1运行之后将等待二进制信号量有效,进程2使信号量有效,继而打印:
semfd write value: 1
semfd read value: 1
结果我们看到写了信号量值1,读到的信号量值也是1,需要注意的是,semfd 并不会传递信号量的值,无论 write 写入的值是多少,read 值将总是1。