SylixOS设备操作之select
通过前面的学习我们知道了SylixOS下操作设备是通过设备文件的方式来进行的,先open一个文件获取到一个文件描述符,然后通过文件描述符进行操作文件。有时候我们在程序里可能需要对多个文件描述符进行操作,比如有一个程序我们需要读取设备数据,如果没有数据准备好程序需要阻塞,同时我们还要检测用户在命令行的输入,如果输入'quit'程序需要退出,程序该如何设计?大家首先想到的接受输入用scanf不就行了么,但是scanf这个函数是阻塞型的,如果用户没有输入东西程序就会一直阻塞,这样即使设备有数据准备好程序也不能及时去处理。
1. select应用层用法简介
解决这类问题的办法就是使用多路IO复用接口select,函数原型:
LW_API INT select(INT iWidth, fd_set *pfdsetRead, fd_set *pfdsetWrite, fd_set *pfdsetExcept, struct timeval *ptmvalTO);
select可以检测一系列的文件描述符集合,检测的方向为三个,分别为可读,可写,有异常,并且还可以带有超时检测机制,这里我们以检测描述符可读为例进行讲解。
在本节demo中,我们设计检测两个描述符是否可读,一个是标准输入描述符,一个是demo设备文件描述符。我们使用fd_set 数据结构来记录描述符的集合,在使用select之前需要初始化这个描述符集合:
FD_ZERO (&fds); FD_SET (STD_IN, &fds); FD_SET (fd, &fds);
首先使用FD_ZERO 将数据结构清零,然后使用FD_SET 依次设置想要检测的描述符,STD_IN 表示标准输入文件描述符,每一个进程都会默认打开标准输入、标准输出、标准出错三个文件描述符,在SylixOS下,这三个文件描述符分别用STD_IN 、STD_OUT 、STD_ERR 来表示:
/********************************************************************************************************* low-level I/O input, output, error fd's *********************************************************************************************************/ #define STD_IN 0 #define STD_OUT 1 #define STD_ERR 2
初始化完描述符集合后就可以调用select来检测:
ret = select (fd + 1, &fds, NULL, NULL, NULL);
select第一个参数为要检测的文件描述符中最大的那个值再加一,当描述符可读后还需要调用FD_ISSET 来确定是哪个描述符准备好了:
if (FD_ISSET(fd, &fds)) { printf("data readable.\r\n"); } if (FD_ISSET(0, &fds)) { printf("app demo quit.\r\n"); break; }
2. 驱动层支持select
SylixOS中select是通过底层驱动ioctl来实现的,select实现伪代码表示的基本流程:
select | ioctl调用FIOSELECT命令 | 线程睡眠 | ioctl调用FIOUNSELECT命令 | 结束
驱动里select方法的阻塞和唤醒是通过select链表来实现的,首先在demo_dev_t 数据结构中添加select链表wakeup_list :
typedef struct _demo_dev { LW_DEV_HDR dev; int flag; LW_SEL_WAKEUPLIST wakeup_list; void *priv; } demo_dev_t;
然后在驱动加载时通过SEL_WAKE_UP_LIST_INIT 来初始化这个select链表:
SEL_WAKE_UP_LIST_INIT(&demo_dev[0].wakeup_list); SEL_WAKE_UP_LIST_INIT(&demo_dev[1].wakeup_list);
如果驱动程序中数据还没准备好,就需要将线程睡眠,这是通过将当前驱动的等待结点加入到等待链表中来实现的:
PLW_SEL_WAKEUPNODE pselnode; pselnode = (PLW_SEL_WAKEUPNODE)arg; SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode);
等待结点pselnode 是select在调用驱动的ioctl之前就准备好的,并作为ioctl的arg参数传进来,驱动层只需要转换成PLW_SEL_WAKEUPNODE 类型指针即可使用。
注意:SEL_WAKE_NODE_ADD只是将等待结点加入到等待链表中,此接口并不会阻塞,线程睡眠是通过线程TCB中的一个二进制信号量来实现的。
如果数据准备好了,就可以通过SEL_WAKE_UP 或者SEL_WAKE_UP_ALL 来唤醒线程,其中SEL_WAKE_UP_ALL 可以通过指定唤醒类型来进行更精确的控制。在实际的设备驱动中,一般是在中断处理中来调用此接口进行唤醒线程。在本例中,我们将唤醒调用放在虚拟设备的定时处理线程中:
static LW_SEL_WAKEUPLIST *wakeup_list; static void *vdev_thread (void *sarg) { while (1) { sleep(READ_PERIOD); data++; readable = TRUE; API_SemaphoreBPost(read_sync); if (wakeup_list) { SEL_WAKE_UP_ALL(wakeup_list, SELREAD); } } return NULL; }
wakeup_list 这个全局变量在驱动调用vir_device_wakeup_register 接口时初始化:
int vir_device_wakeup_register (LW_SEL_WAKEUPLIST *list) { wakeup_list = list; return 0; }
线程唤醒后需要将之前的等待结点从等待链表里删除,这是通过SEL_WAKE_NODE_DELETE 来实现的:
PLW_SEL_WAKEUPNODE pselnode; pselnode = (PLW_SEL_WAKEUPNODE)arg; SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode);
通过前面的介绍,我们可以写出demo驱动ioctl中FIOSELECT 和FIOUNSELECT 命令相对应的处理:
case FIOSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { if (vir_device_readable()) { SEL_WAKE_UP(pselnode); } else { SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode); vir_device_wakeup_register(&dev->wakeup_list); } } else { return (PX_ERROR); } break; case FIOUNSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode); } else { return (PX_ERROR); } break;
当调用select时驱动已经有准备好的数据时,需要调用SEL_WAKE_UP 告知系统已经有数据准备好,线程不需要阻塞。
3. 实际运行效果
加载驱动后,运行程序,终端输出打印:
[root@sylixos:/apps/app_demo12]# ./app_demo12 demo_open . open read/write. file permission: 644. demo_ioctl 1c. demo_ioctl 1d. data readable. demo_read. read data is: 3162. demo_ioctl 1c. demo_ioctl 1d. data readable. demo_read. read data is: 3163. demo_ioctl 1c. demo_ioctl 1d. data readable. demo_read. read data is: 3164. demo_ioctl 1c. quit demo_ioctl 1d. app demo quit. demo_close. [root@sylixos:/apps/app_demo12]#
可以看出在没有输入时,程序依然是每隔一段时间读取数据打印输出,如果命令行有输入,比如输入'quit'后,程序响应输入直接退出结束。
附源码
driver_demo12.c源码:
#define __SYLIXOS_KERNEL #include <SylixOS.h> #include <module.h> #include <string.h> #include "driver.h" #include "vir_peripheral_device.h" typedef struct _demo_dev { LW_DEV_HDR dev; int flag; LW_SEL_WAKEUPLIST wakeup_list; void *priv; } demo_dev_t; int drv_index; demo_dev_t demo_dev[2]; static long demo_open(LW_DEV_HDR *dev, char *name, int flag, int mode) { demo_dev_t *device = (demo_dev_t *)dev; device->priv = NULL; device->flag = 0; printk("%s %s.\r\n", __func__, name); if (flag & O_NONBLOCK) { printk("open nonblock.\r\n"); device->flag |= O_NONBLOCK; } if ((flag & O_ACCMODE) == O_RDONLY) { printk("open read only.\r\n"); } else if ((flag & O_ACCMODE) == O_WRONLY) { printk("open write only.\r\n"); } else { printk("open read/write.\r\n"); } printk("file permission: %o.\r\n", mode); LW_DEV_INC_USE_COUNT(&device->dev); return (long)device; } static int demo_close(demo_dev_t *dev) { printk("%s.\r\n", __func__); LW_DEV_DEC_USE_COUNT(&dev->dev); return ERROR_NONE; } static int demo_ioctl(demo_dev_t *dev, int cmd, long arg) { PLW_SEL_WAKEUPNODE pselnode; struct stat *pstat; struct data *data; printk("%s %x.\r\n", __func__, cmd); switch (cmd) { case FIOFSTATGET: pstat = (struct stat *)arg; if (!pstat) { return (PX_ERROR); } pstat->st_dev = LW_DEV_MAKE_STDEV(&dev->dev); pstat->st_ino = (ino_t)0; pstat->st_mode = (S_IRWXU | S_IRWXG | S_IRWXO | S_IFCHR); pstat->st_nlink = 1; pstat->st_uid = 0; pstat->st_gid = 0; pstat->st_rdev = 0; pstat->st_size = 0; pstat->st_blksize = 0; pstat->st_blocks = 0; pstat->st_atime = API_RootFsTime(LW_NULL); pstat->st_mtime = API_RootFsTime(LW_NULL); pstat->st_ctime = API_RootFsTime(LW_NULL); break; case FIONBIO: if (*(int *)arg) { dev->flag |= O_NONBLOCK; } else { dev->flag &= ~O_NONBLOCK; } break; case FIOSETFL: if ((int)arg & O_NONBLOCK) { dev->flag |= O_NONBLOCK; } else { dev->flag &= ~O_NONBLOCK; } break; case FIOSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { if (vir_device_readable()) { SEL_WAKE_UP(pselnode); } else { SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode); vir_device_wakeup_register(&dev->wakeup_list); } } else { return (PX_ERROR); } break; case FIOUNSELECT: pselnode = (PLW_SEL_WAKEUPNODE)arg; if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) { SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode); } else { return (PX_ERROR); } break; case CMD_GET_VERSION: data = (struct data *)arg; data->version = 3; break; default: return (PX_ERROR); } return ERROR_NONE; } static ssize_t demo_read(demo_dev_t *dev, char *buf, size_t size) { int ret = -1; BOOL non_block = FALSE; printk("%s.\r\n", __func__); if (dev->flag & O_NONBLOCK) { non_block = TRUE; } else { non_block = FALSE; } ret = vir_device_read(non_block); if (ret > 0) memcpy(buf, &ret, sizeof(int)); return ret; } static ssize_t demo_write(demo_dev_t *dev, char *buf, size_t size) { char wdata[30] = {0}; int wdata_len = 30; printk("%s.\r\n", __func__); wdata_len = (size < wdata_len) ? size : wdata_len; memcpy(wdata, buf, size); printk("write data %s success.\r\n", wdata); return wdata_len; } static struct file_operations demo_fops = { .owner = THIS_MODULE, .fo_open = demo_open, .fo_close = demo_close, .fo_ioctl = demo_ioctl, .fo_read = demo_read, .fo_write = demo_write, }; int module_init (void) { int ret = 0; drv_index = iosDrvInstallEx(&demo_fops); if (drv_index < 0) { printk("driver install fail.\r\n"); return -1; } DRIVER_LICENSE(drv_index, "GPL->Ver 2.0"); DRIVER_AUTHOR(drv_index, "GeWenBin"); DRIVER_DESCRIPTION(drv_index, "demo driver."); ret = iosDevAdd(&demo_dev[0].dev, "/dev/demo0", drv_index); if (ret != ERROR_NONE) { printk("device add fail.\r\n"); iosDrvRemove(drv_index, TRUE); return -1; } ret = iosDevAdd(&demo_dev[1].dev, "/dev/demo1", drv_index); if (ret != ERROR_NONE) { printk("device1 add fail.\r\n"); iosDevDelete(&demo_dev[0].dev); iosDrvRemove(drv_index, TRUE); return -1; } SEL_WAKE_UP_LIST_INIT(&demo_dev[0].wakeup_list); SEL_WAKE_UP_LIST_INIT(&demo_dev[1].wakeup_list); vir_device_init(); return 0; } void module_exit (void) { iosDevDelete(&demo_dev[0].dev); iosDevDelete(&demo_dev[1].dev); iosDrvRemove(drv_index, TRUE); }
vir_peripheral_device.c源码:
/* * vir_peripheral_device.c * * Created on: Nov 28, 2020 * Author: Administrator */ #define __SYLIXOS_KERNEL #include <SylixOS.h> #include "vir_peripheral_device.h" #define READ_PERIOD (3) #define MUTEX_CREATE_DEF_FLAG \ LW_OPTION_WAIT_PRIORITY | \ LW_OPTION_INHERIT_PRIORITY | \ LW_OPTION_DELETE_SAFE | \ LW_OPTION_OBJECT_GLOBAL static LW_SEL_WAKEUPLIST *wakeup_list; static LW_HANDLE tid; static LW_HANDLE read_sync; static BOOL readable = FALSE; static int data = 1; static void *vdev_thread (void *sarg) { while (1) { sleep(READ_PERIOD); data++; readable = TRUE; API_SemaphoreBPost(read_sync); if (wakeup_list) { SEL_WAKE_UP_ALL(wakeup_list, SELREAD); } } return NULL; } int vir_device_init (void) { LW_CLASS_THREADATTR threadattr; API_ThreadAttrBuild(&threadattr, 4 * LW_CFG_KB_SIZE, LW_PRIO_NORMAL, LW_OPTION_THREAD_STK_CHK, LW_NULL); tid = API_ThreadCreate("t_vdev", vdev_thread, &threadattr, LW_NULL); if (tid == LW_OBJECT_HANDLE_INVALID) { return (PX_ERROR); } read_sync = API_SemaphoreBCreate("count_lock", 0, LW_OPTION_OBJECT_LOCAL, LW_NULL); if (read_sync == LW_OBJECT_HANDLE_INVALID) { API_ThreadDelete(&tid, NULL); printk("semaphore create failed.\n"); return (PX_ERROR); } return ERROR_NONE; } int vir_device_deinit (void) { API_ThreadDelete(&tid, NULL); return ERROR_NONE; } int vir_device_read (BOOL non_block) { if (non_block) { if (readable) { goto return_data; } else { return 0; } } API_SemaphoreBPend(read_sync, LW_OPTION_WAIT_INFINITE); return_data: readable = FALSE; return data; } BOOL vir_device_readable (void) { return readable; } int vir_device_wakeup_register (LW_SEL_WAKEUPLIST *list) { wakeup_list = list; return 0; }
vir_peripheral_device.h源码:
/* * vir_peripheral_device.h * * Created on: Nov 28, 2020 * Author: Administrator */ #ifndef SRC_VIR_PERIPHERAL_DEVICE_H_ #define SRC_VIR_PERIPHERAL_DEVICE_H_ int vir_device_init(void); int vir_device_deinit(void); int vir_device_read(BOOL non_block); BOOL vir_device_readable(void); int vir_device_wakeup_register(LW_SEL_WAKEUPLIST *list); #endif /* SRC_VIR_PERIPHERAL_DEVICE_H_ */
app_demo12源码:
#include <stdio.h> #include <string.h> int main (int argc, char **argv) { int fd; int ret = 0; int data = -1; fd_set fds; fd = open("/dev/demo0", O_RDWR); if (fd < 0) { printf("open file fail.\r\n"); return -1; } while (1) { FD_ZERO (&fds); FD_SET (STD_IN, &fds); FD_SET (fd, &fds); ret = select (fd + 1, &fds, NULL, NULL, NULL); if (ret < 0) { printf("select fail.\r\n"); close(fd); return -1; } else if (ret == 0){ printf("select timeout.\r\n"); } else { if (FD_ISSET(fd, &fds)) { printf("data readable.\r\n"); } if (FD_ISSET(STD_IN, &fds)) { printf("app demo quit.\r\n"); break; } } read(fd, &data, 4); printf("read data is: %d.\r\n", data); } close(fd); return (0); }
评论