虚拟设备文件

更新时间:
2024-07-04
下载文档

虚拟设备文件

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。

文档内容是否对您有所帮助?
有帮助
没帮助