5.3RTDM用户层驱动

5.3 RTDM用户层驱动

5.3.1 UDD原理及框架

1. 核心思想

Xenomai 基于 RTDM 实现了 UDD(User-space Device Driver Framework)框架,它的核心思想是通过内存映射将硬件寄存器等物理内存区域映射到用户进程的虚拟地址空间,同时提供中断通知处理机制,使用户态程序能够及时响应硬件中断。

UDD 框架核心 kernel/drivers/udd/udd.c,为编写UDD设备驱动程序提供了中断控制和 I/O 内存访问接口。使用这些接口,可以高效地为硬件设备编写 mini-drivers 迷你驱动程序。

mini-drivers 迷你驱动程序,位于内核态,应当处理特定 I/O 卡/设备的内存映射和中断控制等功能。关于中断控制,迷你驱动程序只需完成中断源的基本支持,这样大部分设备请求都可以由运行在用户空间的 Xenomai 应用程序来处理。通常情况下,迷你驱动程序负责处理中断的上半部分(top-half),而用户空间应用程序则负责处理中断的下半部分(bottom-half)。

2. 内存映射机制

在Xenomai UDD框架中,内核态的UDD驱动负责将硬件设备的物理内存地址映射到用户态进程的虚拟地址空间。用户态驱动程序通过mmap()函数调用,将设备节点对应的物理内存地址映射到自身进程空间。

3. 中断处理机制

UDD框架提供了两种中断通知方式:一种是基于select()或epoll()等同步I/O多路复用机制,等待中断事件并结合read()进行中断应答;另一种是为中断注册信号,使用sigtimedwait()等待信号响应中断。

综上所述,UDD 的最大优势在于其中断处理的实时性。UDD基于 RTDM和Xenomai实时调度机制,确保了从硬件中断产生到用户态程序响应的全路径为实时上下文,从而满足了实时应用对中断响应时间的严格要求。

5.3.2 UDD 设备定义

udd_device 结构体描述了 UDD 设备的属性。它包含了设备的名称、子类、标志、内存区域、中断信息等。

c 复制代码
struct udd_device {
	const char *device_name;
	int device_flags;
	int device_subclass;

	struct {
		int (*open)(struct rtdm_fd *fd, int oflags);
		void (*close)(struct rtdm_fd *fd);
		int (*ioctl)(struct rtdm_fd *fd,
			     unsigned int request, void *arg);
		int (*mmap)(struct rtdm_fd *fd,
			    struct vm_area_struct *vma);
		int (*interrupt)(struct udd_device *udd);
	} ops;

	int irq;

	struct udd_memregion mem_regions[UDD_NR_MAPS];

	struct udd_reserved {
		rtdm_irq_t irqh;
		u32 event_count;
		struct udd_signotify signfy;
		struct rtdm_event pulse;
		struct rtdm_driver driver;
		struct rtdm_device device;
		struct rtdm_driver mapper_driver;
		struct udd_mapper {
			struct udd_device *udd;
			struct rtdm_device dev;
		} mapdev[UDD_NR_MAPS];
		char *mapper_name;
		int nr_maps;
	} __reserved;
};
1. 基本属性
  • device_name:设备的名称字符串,传递到 struct rtdm_device 结构体中的 label 变量。
  • device_subclass:设备的子类标识符,传递到 struct rtdm_profile_info 中的 subclass_id 变量。设备主类代码在 UDD 中已预设为 RTDM_CLASS_UDD。
  • device_flags:此变量代表额外的设备的标志位,传递到 struct rtdm_driver 结构体中的 device_flags 变量,例如 RTDM_EXCLUSIVE。不需要传递 RTDM_NAMED_DEVICE 命名设备标志,因为 UDD 内部总是会自动设置。
  • __reserved:内核保留的 udd_reserved 结构体,包含了与 udd_device 相关的内部管理信息,如驱动描述符、内存映射数量、事件计数、中断处理句柄等,用于 UDD 内部管理。
2. 内存区域 struct udd_memregion mem_regions[UDD_NR_MAPS]

mem_regions 是一个包含多个内存区域的数组:

复制代码
#define UDD_NR_MAPS  5

UDD_NR_MAPS 定义为 5, 代表 UDD 设备可以定义 5 个不同的内存区域,可以基本满足硬件设备需求。

c 复制代码
struct udd_memregion {
	const char *name;

	unsigned long addr;

	size_t len;

	int type;
};
  • name: 内存区域的名称,虽然不是强制要求,但为了信息的完整性,建议为每个内存区域指定一个唯一的名称。
  • addr: 内存区域的起始地址,这个地址可以是物理地址也可以是虚拟地址,取决于内存区域的类型。
  • len : 内存区域的长度,单位是字节,并且必须是PAGE_SIZE对齐的。
  • type : 内存区域的类型,可能的类型包括物理内存(UDD_MEM_PHYS)、虚拟内存(UDD_MEM_VIRTUAL)等。
    • UDD_MEM_NONE 0 :无内存区域,用于禁用udd_device.mem_regions[]数组中的某个条目。
    • UDD_MEM_PHYS 1 :物理I/O内存区域,表示设备的内存区域为物理地址映射的I/O空间,UDD核心默认通过调用 rtdm_mmap_iomem() 服务将其映射到用户虚拟地址空间。
    • UDD_MEM_LOGICAL 2 :内核逻辑内存区域,例如通过kmalloc()分配的内存,UDD核心默认通过调用rtdm_mmap_kmem()服务将其映射到用户虚拟地址空间。
    • UDD_MEM_VIRTUAL 3 :无直接物理映射的虚拟内存区域,例如通过vmalloc()分配的内存,UDD核心默认通过调用rtdm_mmap_vmem()服务将其映射到用户虚拟地址空间。
3. 中断

udd_device.irq 字段用于指定设备的中断(IRQ)。这个字段可以取一些特殊的值,这些值定义了中断的管理方式。

  1. UDD_IRQ_NONE (0)

表示没有管理中断。传递这个值会隐式地禁用所有与中断相关的服务,包括中断控制(启用/禁用)和通知。

适用于不需要中断处理的设备。通过设置 udd->irqUDD_IRQ_NONE,可以确保设备不会尝试处理中断事件,从而避免不必要的错误和资源占用。

  1. UDD_IRQ_CUSTOM (-1)

表示中断由迷你驱动程序直接管理,完全不依赖 UDD 核心 所提供的中断管理接口。

适用于需要自定义中断处理机制的设备。使用 UDD_IRQ_CUSTOM 允许迷你驱动程序完全控制中断的管理,包括中断的注册、启用、禁用以及中断事件的通知机制。

  1. 有效的中断号

除了上述两个特殊的值外,udd->irq 字段还可以取其他有效的中断号,这意味着设备将使用特定的中断号进行中断处理。

如果 udd->irq 不是 UDD_IRQ_NONEUDD_IRQ_CUSTOM,则必须提供有效的中断处理程序 udd->ops.interrupt,否则会导致 -EINVAL 错误。

4. 操作函数

ops 结构体用于定义设备操作的回调函数。这些回调函数提供了一种机制,使得驱动程序可以根据设备的需求来处理特定的操作。以下是 ops 结构体中各个成员函数的详细解释:

c 复制代码
struct {
    int (*open)(struct rtdm_fd *fd, int oflags);
    void (*close)(struct rtdm_fd *fd);
    int (*ioctl)(struct rtdm_fd *fd, unsigned int request, void *arg);
    int (*mmap)(struct rtdm_fd *fd, struct vm_area_struct *vma);
    int (*interrupt)(struct udd_device *udd);
} ops;
  • open 函数
c 复制代码
int (*open)(struct rtdm_fd *fd, int oflags);

作用open 函数是一个可选的回调函数,用于处理设备的打开操作。在调用这个函数时,传入的参数包括一个指向 RTDM 文件描述符结构 rtdm_fd 的指针以及打开标志 oflags

注意事项:此函数仅在次模式(secondary mode)下被调用。次模式是指在内核线程中运行的、不具有实时性要求的模式。

  • close 函数
c 复制代码
void (*close)(struct rtdm_fd *fd);

作用close 函数也是一个可选的回调函数,用于处理设备的关闭操作。调用此函数时传入的参数是一个指向 RTDM 文件描述符结构 rtdm_fd 的指针。

注意事项 :与 open 类似,此函数同样仅在次模式下被调用。

  • ioctl 函数
c 复制代码
int (*ioctl)(struct rtdm_fd *fd, unsigned int request, void *arg);

作用ioctl 函数是一个可选的回调函数,用于处理设备的控制操作。传入的参数包括指向 RTDM 文件描述符结构 rtdm_fd 的指针、控制请求 request 和一个参数指针 arg

行为 :如果 ioctl 函数返回 -ENOSYS,则 UDD 核心会自动应用默认操作,就像没有定义 ioctl 处理程序一样。

注意事项:此函数仅在主模式(primary mode)下被调用。主模式是指在内核线程中运行的、具有实时性要求的模式。

  • mmap 函数
c 复制代码
int (*mmap)(struct rtdm_fd *fd, struct vm_area_struct *vma);

作用mmap 函数是一个可选的回调函数,用于处理设备的内存映射操作(mapper 设备)。传入的参数包括指向 RTDM 文件描述符结构 rtdm_fd 的指针和指向虚拟内存区域结构 vm_area_struct 的指针。

行为 :如果 mmap 函数为 NULL,则 UDD 核心会根据定义的内存类型自动建立映射。

注意事项:此函数仅在次模式下被调用。

  • interrupt 函数
c 复制代码
int (*interrupt)(struct udd_device *udd);

作用interrupt 函数是一个可选的回调函数,用于接收中断。如果驱动程序将中断处理交由 UDD 核心管理(通过设置 irq 字段为一个有效的值,且该值不同于 UDD_IRQ_CUSTOMUDD_IRQ_NONE),则必须提供此函数。

行为:该函数的返回值可以是以下几种之一:

  • RTDM_IRQ_HANDLED:如果驱动程序成功处理了中断,可以返回此值。此外,可以结合 RTDM_IRQ_DISABLE 标志来防止 Cobalt 内核在返回后重新启用中断线,否则中断线会自动重新启用。
  • RTDM_IRQ_NONE:如果中断与驱动程序能够处理的中断类型不匹配,可以返回此值。

注意事项:此函数仅在主模式下被调用。处理完中断后,UDD 核心会通知等待中断事件的用户空间 Cobalt 线程(如果有的话)。

5.3.3 UDD 设备注册

udd_register_device 向 UDD 核心注册一个UDD设备及其迷你驱动程序,使其能够被用户态应用程序访问。udd_unregister_device 用于注销一个UDD设备及其迷你驱动程序。

以下对udd_register_device函数展开分析。

1. 参数校验阶段
c 复制代码
if (udd->device_flags & RTDM_PROTOCOL_DEVICE)
    return -EINVAL;
  • 禁止协议设备类型(RTDM_PROTOCOL_DEVICE)
  • UDD设备必须使用RTDM_NAMED_DEVICE标志
c 复制代码
for (n = 0, ur->nr_maps = 0; n < UDD_NR_MAPS; n++) {
    rn = udd->mem_regions + n;
    if (rn->type == UDD_MEM_NONE) continue;
    ret = check_memregion(udd, rn);
  • 遍历内存区域描述符数组(UDD_NR_MAPS=5
  • 调用check_memregion()验证每个非空区域的合法性(地址/长度/名称)
2. RTDM驱动初始化
c 复制代码
drv->profile_info = (struct rtdm_profile_info)
    RTDM_PROFILE_INFO(udd->device_name, RTDM_CLASS_UDD,
                udd->device_subclass, 0);
drv->device_flags = RTDM_NAMED_DEVICE|udd->device_flags;
drv->device_count = 1;
drv->context_size = sizeof(struct udd_context);
drv->ops = (struct rtdm_fd_ops){
    .open = udd_open,
    .ioctl_rt = udd_ioctl_rt,
    .read_rt = udd_read_rt,
    .write_rt = udd_write_rt,
    .close = udd_close,
    .select = udd_select,
};
  1. rtdm_profile_info 驱动程序的配置信息,如驱动名称、类别、子类别和版本号。
  • 类别:固定为预先定义的 RTDM_CLASS_UDD
  • 子类别:来自于 UDD 设备的 udd->device_subclass 变量
  1. device_flags 设备标志
  • RTDM_NAMED_DEVICE 表明设备是命名设备。
  • 额外设备标志,来自于 UDD 设备的 udd->device_flags 变量
  1. 设置文件操作集,由 UDD 核心提供的操作函数
  • udd_open:实时设备驱动打开操作的实现,负责初始化设备上下文,调用 UDD 设备迷你驱动自定义的open回调函数(如过存在)。

  • udd_close:实现实时设备驱动的关闭操作,执行 UDD 设备迷你驱动自定义的close回调函数(如果存在)。

    完成设备实例的清理工作

  • udd_ioctl_rt:处理实时设备驱动的I/O控制命令,内部已经实现信号绑定、中断使能/禁用等关键操作,通过回调UDD 设备迷你驱动自定义的ioctl函数来扩展IO控制命令。

  • udd_read_rt:该函数主要用于获取事件信号,并读取 UDD 设备的中断事件计数,并将该计数传递给用户空间。

    • 如果设备的中断事件计数 event_count 已经发生变化,则清除中断事件信号,并返回中断事件计数到用户空间。
    • 如果设备的中断事件计数 event_count 未发送变化,则一直等待中断事件信号,直到事件信号发生,并返回中断事件计数到用户空间。
  • udd_write_rt:根据用户提供的参数 val 的值调用 udd_ioctl_rt 函数,控制 UDD 设备的中断使能状态。

    • 如果 val 不为零,则开启中断(UDD_RTIOC_IRQEN)。
    • 如果 val 为零,则关闭中断(UDD_RTIOC_IRQDIS)。
  • udd_select:是一个用于实时设备文件描述符选择操作的函数。

    • 函数调用 rtdm_event_select 来实现事件选择,将设备的中断事件与选择器关联起来。这样,当设备上的中断事件发生时,选择器可以被通知。
    • 使得用户空间的应用程序可以通过选择 select 机制来监控设备上的中断事件,从而在用户空间处理中断。
3. RTDM 设备注册
c 复制代码
dev->driver = drv;
dev->label = udd->device_name;
ret = rtdm_dev_register(dev);
  • RTDM 设备名称,来自于 UDD 设备 udd->device_name 成员。
  • 调用RTDM核心函数 rtdm_dev_register() 将设备挂载到RTDM设备树,在用户层可以找到 /dev/rtdm/device_name 设备。
4. 建立内存映射设备
c 复制代码
if (ur->nr_maps > 0) {
    ret = register_mapper(udd);
    if (ret) goto fail_mapper;
}

函数 register_mapper 是用于为 UDD 设备额外注册内存映射设备,

  1. 设备名称格式化

    • ur->mapper_name = kasformat("%s,mapper%%d", udd->device_name);:使用 kasformat 函数格式化设备名称,将设备名称与 "mapper%d" 结合,其中 %d 是一个占位符,后面会用数字替换。
    • 设备名称举例:/dev/rtdm/device_name,mapper0/dev/rtdm/device_name,mapper1
  2. 设置驱动程序信息

    • 设备类别:RTDM_CLASS_MEMORY
    • 设备类型:命令设备。
    • drv->ops:设置文件操作集,包括打开、关闭和内存映射的操作函数。
  3. 设备操作集

    c 复制代码
    drv->ops = (struct rtdm_fd_ops){
        .open		=	mapper_open,
        .close		=	mapper_close,
        .mmap		=	mapper_mmap,
    };
    • mapper_open/mapper_close: 分别用于打开和关闭内存映射设备
    • mapper_mmap:用于为 UDD 设备中定义的 struct udd_memregion mem_regions[UDD_NR_MAPS] 内存区域完成内存映射,方便用户层访问。
      • mapper_mmap 自身支持 struct udd_memregion mem_regions[UDD_NR_MAPS] 中定义的所有类型内存区域,无需额外的代码开发
      • 如果在 UDD 设备注册时实现 udd->ops.mmap 函数,则 mapper_mmap 会调用 udd->ops.mmap 处理内存映射,不会执行自身的内存映射代码。
5. 中断配置
c 复制代码
if (udd->irq != UDD_IRQ_NONE && udd->irq != UDD_IRQ_CUSTOM) {
    ret = rtdm_irq_request(&ur->irqh, udd->irq,
               udd_irq_handler, 0, dev->name, udd);
}

使用 RTDM 中断注册API(rtdm_irq_request)为有效中断号 udd->irq 注册中断服务程序为udd_irq_handler

c 复制代码
static int udd_irq_handler(rtdm_irq_t *irqh)
{
	struct udd_device *udd;
	int ret;

	udd = rtdm_irq_get_arg(irqh, struct udd_device);
	ret = udd->ops.interrupt(udd);
	if (ret == RTDM_IRQ_HANDLED)
		udd_notify_event(udd);

	return ret;
}
  • 调用设备中断处理回调函数。udd 是代表当前 UDD 设备。udd->ops.interrupt 指向具体的中断处理回调函数。

    c 复制代码
    ret = udd->ops.interrupt(udd);
  • 检查中断处理结果并完成中断事件通知。如果中断处理回调函数返回 RTDM_IRQ_HANDLED,表示中断上半部已被成功处理。调用 udd_notify_event 函数来完成中断事件通知。

    c 复制代码
    if (ret == RTDM_IRQ_HANDLED)
        udd_notify_event(udd);

5.3.4 用户层 mmap 操作

mini-driver 注册时,UDD会自动为它创建一个映射设备。这个映射设备会在RTDM命名空间中为每个声明的内存区域创建特殊的文件,应用程序可以通过打开这些文件并使用mmap(2)系统调用来映射这些内存区域到自己的地址空间。

以下是一个具体的例子,展示了如何在索引为2的内存区域数组中声明一个物理内存区域:

c 复制代码
static struct udd_device udd;

static int foocard_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
      udd.device_name = "foocard";
      ...
      udd.mem_regions[2].name = "ADC";
      udd.mem_regions[2].addr = pci_resource_start(dev, 1);
      udd.mem_regions[2].len = pci_resource_len(dev, 1);
      udd.mem_regions[2].type = UDD_MEM_PHYS;
      ...
      return udd_register_device(&udd);
}

在这个例子中,foocard_pci_probe函数用于初始化一个名为foocard的设备,并在该设备的内存区域数组的第二个位置声明了一个名为ADC的物理内存区域。这个内存区域的起始地址和长度分别是通过pci_resource_startpci_resource_len函数获取的。

一旦mini-driver声明了内存区域并成功注册,应用程序就可以通过以下代码来访问这个内存区域:

c 复制代码
int fd, fdm;
void *p;

fd = open("/dev/rtdm/foocard", O_RDWR);
fdm = open("/dev/rtdm/foocard,mapper2", O_RDWR);
p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fdm, 0);

在这段代码中,应用程序首先打开设备文件/dev/rtdm/foocard,然后通过/dev/rtdm/foocard,mapper2文件来打开内存映射设备,最后使用mmap函数将内存区域映射到自己的地址空间。通过这种方式,应用程序可以直接访问到设备的内存区域。

5.3.5 用户层中断编程

1. 中断管理

在上文中已经介绍,udd_ioctl_rt 实现了处理实时设备驱动的I/O控制命令,内部已经实现信号绑定、中断使能/禁用等关键操作。

在应用程序编程时,包含头文件 /usr/xenomai/include/rtdm/uapi/udd.h 头文件,即可在 ioctl() 使用如下预定义的IO控制命令。

c 复制代码
#define UDD_RTIOC_IRQEN     _IO(RTDM_CLASS_UDD, 0)      // 无参数命令
#define UDD_RTIOC_IRQDIS    _IO(RTDM_CLASS_UDD, 1)      // 无参数命令
#define UDD_RTIOC_IRQSIG    _IOW(RTDM_CLASS_UDD, 2, struct udd_signotify) // 写方向命令
  1. 启用和关闭中断
  • UDD_RTIOC_IRQEN :启用中断线,
    • udd_ioctl_rt 函数最终调用 udd_enable_irq 启用中断线。
  • UDD_RTIOC_IRQDIS :禁用中断线,
    • udd_ioctl_rt 函数最终调用 udd_disable_irq 禁用中断线。
  1. 启用/禁用中断事件发生时的信号通知 singal

IO控制命令 UDD_RTIOC_IRQSIG 需要传递 struct udd_signotify 类型的变量到 udd_ioctl_rt函数。

复制代码
struct udd_signotify {
	pid_t pid;
	int sig;
};

struct udd_signotify 结构体用于 UDD 中断事件的信号通知机制。

  • pid_t pid:指定的Cobalt线程的进程ID。如果该值为零或负数,信号通知将被禁用。
    • 如果 pid 字段为零或负数,则禁用通知。
    • 如果 pid 字段为一个有效的正数,那么指定的Cobalt线程(其进程ID为 pid 指定的值)将会在中断发生时接收一个信号。
  • int sig:要发送给指定线程的信号编号。只有当 pid 为正数时,这个字段才会被考虑。
    • 信号编号必须在 [SIGRTMIN .. SIGRTMAX] 范围内。
    • Cobalt线程必须通过 sigwaitinfo()sigtimedwait() 系统调用来显式等待通知,不能以异步模式接收通知。
2. 中断下半部处理机制之一 select

基于 RTDM skin 中实现的 select() 函数,可以使用I/O多路复用机制,监听多个 UDD 设备文件描述符,判断它们是否准备好执行非阻塞读操作。类似下面的示例代码,演示了同步I/O多路复用机制,等待中断事件并结合read()进行中断下半部处理。

c 复制代码
int fd = open("/dev/rtdm/foocard", O_RDWR | O_NONBLOCK);

int ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);

ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
  1. select 等待中断事件

应用程序调用的 select 最终调用到 UDD 驱动中的 udd_select 函数。

复制代码
static int udd_select(struct rtdm_fd *fd, struct xnselector *selector,
		      unsigned int type, unsigned int index)
{
    ...snips...

	return rtdm_event_select(&udd->__reserved.pulse,
				 selector, type, index);
}

从如上代码片段可知,rtdm_event_select 在等待事件 rtdm_event_t pulse

  1. 中断服务程序 udd_irq_handler 触发中断事件

如上文所介绍,中断服务程序 udd_irq_handler 会调用 udd_notify_event 函数来完成中断事件通知。其中一种通知方式是触发中断事件 rtdm_event_t pulse 即如下代码段中的 ur->pulse 。同时,中断事件计数器 ur->event_count 会累加。

复制代码
    struct udd_reserved *ur = &udd->__reserved;

	cobalt_atomic_enter(ctx);
	ur->event_count++;
	rtdm_event_signal(&ur->pulse);
	cobalt_atomic_leave(ctx);
  1. read 读取中断事件

应用程序调用的 select 最终调用到 UDD 驱动中的 udd_read_rt 函数。

udd_read_rt 函数中,如果设备的中断事件计数 event_count 已经发生变化,则调用 rtdm_event_clear 清除中断事件信号,并返回中断事件计数到用户空间。

3. 中断下半部处理机制之二 signal

在应用程序中,可以为某个 signal 信号注册一个信号响应函数来处理中断下半部。

代码示例如下:

c 复制代码
int main() {
    int fd;
    struct udd_signotify udd_sig;
    volatile sig_atomic_t event_count = 0;
    siginfo_t info;
    sigset_t mask;

    // 打开设备文件(假设设备文件为 /dev/rtdm/foocard)
    fd = open("/dev/rtdm/foocard", O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 设置要传递的信号信息
    udd_sig.pid = getpid();  // 使用当前进程的pid,假设当前进程是Cobalt线程
    udd_sig.sig = SIGRTMIN;  // 使用SIGRTMIN作为信号编号

    // 通过ioctl传递信号信息
    if (ioctl(fd, UDD_RTIOC_IRQSIG, &udd_sig) == -1) {
        perror("ioctl");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 初始化信号集
    sigemptyset(&mask);
    sigaddset(&mask, udd_sig.sig);

    // 阻塞指定的信号
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
        perror("sigprocmask");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Waiting for signal...\n");

    // 使用sigwaitinfo等待信号
    while (1) {
        if (sigwaitinfo(&mask, &info) == -1) {
            perror("sigwaitinfo");
            close(fd);
            exit(EXIT_FAILURE);
        }

        // 获取从内核传递的值
        event_count = info.si_value.sival_int;
        printf("Received signal %d with event count %d\n", info.si_signo, event_count);
    }

    close(fd);
    return 0;
}
  1. 打开设备文件:

    • 使用open系统调用打开设备文件/dev/rtdm/foocard。请确保这个设备文件路径正确,并且用户有权限访问它。
  2. 设置要传递的信号信息:

    • 创建一个struct udd_signotify结构体实例udd_sig
    • 将当前进程的PID赋值给udd_sig.pid,并指定要发送的信号编号为SIGRTMIN
  3. 通过ioctl传递信号通知信息:

    • 使用ioctl系统调用,将udd_sig结构体传递给内核模块的udd_ioctl_rt函数,命令号为UDD_RTIOC_IRQSIG
  4. 初始化和阻塞信号集:

    • 创建一个信号集mask,并使用sigemptyset清空它。
    • 使用sigaddset将指定的信号(SIGRTMIN)添加到信号集中。
    • 使用sigprocmask系统调用阻塞指定的信号,以确保信号不会被异步处理。
  5. 使用sigwaitinfo等待信号:

    • 使用sigwaitinfo系统调用来等待信号的到来。sigwaitinfo会阻塞当前线程,直到指定的信号到达。
    • 当信号到达时,sigwaitinfo会填充siginfo_t结构体info,从中可以获取事件计数等信息。
    • 打印接收到的信号编号和事件计数。
相关推荐
无聊到发博客的菜鸟5 小时前
STM32 RTC时钟不准的问题
stm32·嵌入式·rtc·rtos
aspirestro三水哥3 天前
4.7POSIX进程与线程实例
rtos·xenomai
无聊到发博客的菜鸟3 天前
使用STM32对SD卡进行性能测试
stm32·单片机·rtos·sd卡·fatfs
切糕师学AI5 天前
Azure RTOS ThreadX 简介
microsoft·嵌入式·azure·rtos
切糕师学AI9 天前
FreeRTOS是什么?
嵌入式·rtos
aspirestro三水哥10 天前
3.5启动QEMUARM64虚拟机
rtos·xenomai
时光の尘11 天前
嵌入式面试八股文(十九)·裸机开发与RTOS开发的区别
linux·stm32·单片机·iic·rtos·spi
aspirestro三水哥13 天前
3.2编译Xenomai内核
rtos·xenomai
Jerry丶Li14 天前
NXP--S32K移植FreeRTOS
嵌入式硬件·rtos·nxp·s32k