当前位置: 首页 > news >正文

平凉市建设局门户网站句容建设局网站

平凉市建设局门户网站,句容建设局网站,怎么样做网站赚钱,广西城乡建设厅证件查询一、阻塞和非阻塞 IO 1. 阻塞和非阻塞简介 这里的 IO 指 Input/Output#xff08;输入/输出#xff09;#xff0c;是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候#xff0c;如果不能获取到设备资源#xff0c;那么阻塞式 IO 就会将对应应用…一、阻塞和非阻塞 IO 1. 阻塞和非阻塞简介 这里的 IO 指 Input/Output输入/输出是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候如果不能获取到设备资源那么阻塞式 IO 就会将对应应用程序对应的线程挂起直到设备资源可以获取为止。非阻塞式 IO应用程序对应的线程不会挂起它要么一直轮询等待知道设备资源可用要么就直接放弃。 下图是阻塞式 IO 访问示意图 应用程序调用 read 函数从设备中读取数据当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒然后从设备中读取数据返回给应用程序。 非阻塞 IO 访问示意图如下 应用程序使用非阻塞访问方式从设备读取数据当设备不可用或数据未准备好的时候会立刻向内核返回一个错误码表示数据读取失败应用程序会再次重新读取数据这样一直重复直到读取数据成功。 使用应用程序来实现阻塞访问 int fd; int data 0;fd open(/dev/xxx_dev, O_RDWR); /* 阻塞方式打开 */ ret read(fd, data, sizeof(data)); /* 读取数据 */ 使用应用程序来实现非阻塞访问 int fd; int data 0;fd open(/dev/xxx_dev, O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */ // O_NONBLOCK参数就是开启非阻塞的关键 ret read(fd, data, sizeof(data)); /* 读取数据 */ 当打开设备的时候默认是阻塞 IO所以之前的实验都是阻塞 IO当在打开设备的时候添加 O_NONBLOCK 参数即可设置为非阻塞方式。 2. 等待队列唤醒阻塞工作 ① 等待队列头 阻塞访问的最大好处就是当设备不可操作进程的时候可以进入休眠这样可以让 CPU 资源让出来。但设备可以操作的时候必须唤醒进程一般在中断函数里完成唤醒工作。 Linux 内核提供了等待队列wait queue来实现阻塞进程的唤醒工作如果我们要在驱动中使用等待队列就必须创建并初始化一个等待队列头等待队列头使用结构体 wait_queue_head 表示。 如果你的代码要考虑移植到老版本 linux 内核中那么最好使用 wait_queue_head_t 表示等待队列头。 定义完成后需要使用 init_waitqueue_head 函数初始化等待队列头 // wq_head 就是要初始化的队列头 void init_waitqueue_head(struct wait_queue_head *wq_head);// 也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化 ② 等待队列项 等待队列头就是一个等待队列的头部每个访问设备的进程都是一个队列项当设备不可用的时候就要将这些进程对应的队列项添加到等待队列里面A设备不可用把A设备对应的项放在等待队列项中。等待队列项结构体 wait_queue_entry 。 使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项 DECLARE_WAITQUEUE(name, tsk);/*name等待队列项的名字tsk等待队列项属于哪个任务进程一般设置为current。在Linux内核中current相当于全局变量表示当前进程 因此宏DECLARE_WAITQUEUE就是给当前运行的进程创建并初始化一个等待队列项。*/ ③ 队列项添加/移除等待队列头 当设备不可访问的时候需要将进程对应的等待队列项添加到前面创建的等待队列头中只有添加到等待队列头中才可以让进程进入休眠态。当设备可以访问的时候再将进程对应的等待队列项从等待队列头中移除。 等待队列项添加/移除 API 函数 /** description : 添加等待队列项到等待队列头里* param - wq_head : 需要加入的等待队列头* param - wq_entry : 需要加入的等待队列项* return : 没有返回值*/ void add_wait_queue(struct wait_queue_head *wq_head,struct wait_queue_entry *wq_entry) /** description : 移除等待队列头中的等待队列项* param - wq_head : 等待队列头* param - wq_entry : 等待队列项* return : 没有返回值*/ void remove_wait_queue(struct wait_queue_head *wq_head,struct wait_queue_entry *wq_entry) ④ 等待唤醒 当设备可以使用的时候就要唤醒进程 void wake_up(struct wait_queue_head *wq_head); void wake_up_interruptible(struct wait_queue_head *wq_head);/* wq_head需要唤醒的等待队列头会将这个队列头的所有进程唤醒区别wake_up能唤醒TASK_INTERRUPTIBLE TASK_INTERRUPTIBLEwake_up_interruptible只能唤醒TASK_INTERRUPTIBLE状态的进程*/ ⑤ 等待事件 除了主动唤醒外也可以设置等待队列去等待某个事件当这个事件满足后就自动唤醒等待队列中的进程。就相当于在睡眠态中如果满足某个事件就自动唤醒队列中的进程。 函数描述wait_event(wq_head, condition)等待以 wq_head 为等待队列头的等待队列被唤醒前提是 condition 条件必须满足(为真)否则一直阻塞。此函数会将进程设置为 TASK_UNINTERRUPTIBLE 状态wait_event_timeout(wq_head, condition, timeout)功能和 wait_event 类似但是此函数可以添加超时时间以 jiffies 为单位。此函数有返回值如果返回 0 的话表示超时时间到而且 condition 为假。为 1 的话表示condition 为真也就是条件满足了。wait_event_interruptible(wq_head, condition)与 wait_event 函数类似但是此函数将进程设置为 TASK_INTERRUPTIBLE就是可以被信号打断。wait_event_interruptible_timeout(wq_head, condition, timeout)与 wait_event_timeout 函数类似此函数也将进程设置为TASK_INTERRUPTIBLE可以被信号打断。 3. 轮询用户空间 如果用户应用程序以非阻塞的方式访问设备设备驱动程序就要提供轮询。应用程序中式是通过 select、epoll 或 poll 函数来查询设备是否可以操作如果可以操作的话从设备读取或向设备写入数据。但在驱动程序中只有 poll 函数。 ① select 函数 /*nfds 所要监视的这三类文件描述集合中最大文件描述符加1readfds、writefds和exceptfds这三个指针指向描述符集合。readfds监视这些文件是否可读只要这些文件里有一个可以读取那么select会返回一个大于0的值表示文件可以读取如果没有文件可以读取那就会根据timeout参数判断是否超时。可以将readfds设置为NULL表示不关心任何文件的读变化writefds和readfds类似exceptfds监视文件的异常timeout 超时时间调用 select 函数等待某些文件描述符可以设置超时时间返回值0表示的话就表示超时发生但是没有任何文件描述符可以进行操作 -1发生错误其他值可以进行操作的文件描述符个数*/ int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout); 超时时间使用结构体 timeval 表示。这里的超时需要定义一个结构体并设置好成员参数再把结构体放在 timeout 上。 比如现在需要从设备文件中读取数据那就可以定义一个 fd_set 变量将这个变量传递给参数 readfds。定义完一个 fd_set 变量后可以使用以下宏进行操作 void FD_ZERO(fd_set *set); // 用户将fd_set变量所有位清零 void FD_SET(int fd, fd_set *set); // 用户将fd_set变量的某个位置1 void FD_CLR(int fd, fd_set *set); // 用户将fd_set变量的某个位清零也就是将一个文件描述符从fd_set中删除 int FD_ISSET(int fd, fd_set *set); // 测试一个文件是否属于某个集合fd就是要判断的文件描述符 使用 select 函数对某个设备驱动进行读非阻塞访问的操作示例如下 void main(void) {int ret, fd; /* 要监视的文件描述符 */fd_set readfds; /* 读操作文件描述符集 */struct timeval timeout; /* 超时结构体 */fd open(dev_xxx, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */FD_ZERO(readfds); /* 清除 readfds */FD_SET(fd, readfds); /* 将 fd 添加到 readfds 里面 *//* 构造超时时间 */timeout.tv_sec 0; // 秒数timeout.tv_usec 500000; /* 500ms */ // 微秒ret select(fd 1, readfds, NULL, NULL, timeout); // 第一个参数 nfds 表示要监视的文件描述符集合中最大的文件描述符值加 1switch (ret) {case 0: /* 超时 */printf(timeout!\r\n);break;case -1: /* 错误 */printf(error!\r\n);break;default: /* 可以读取数据 */if(FD_ISSET(fd, readfds)) { /* 判断是否为 fd 文件描述符 *//* 使用 read 函数读取数据 */}break;} } ② poll 函数 在单个线程中 select 函数能够监视的文件描述符数量有最大的限制一般为 1024虽然可以修改内核将监视的文件描述符数量改大但是这样会降低效率这个时候就可以使用 poll 函数poll 函数本质上和 select 没有太大的差别但是 poll 函数没有最大文件描述符限制 /** param - fds : 要监视的文件描述符集合以及要监视的事件为一个数组数组元素是结构体pollfd类型* param - nfds : poll 函数要监视的文件描述符数量* param - timeout : 超时时间单位为 ms* return : 返回发生事件或错误的文件描述符数量0超时 -1发生错误并且设置 errno 为错误类型*/ int poll(struct pollfd *fds,nfds_t nfds,int timeout); pollfd 结构体如下 struct pollfd {int fd; /* 文件描述符 */short events; /* 请求的事件 */short revents; /* 返回的事件 */ }; fd 是要监视的文件描述符如果 fd 无效的话那么 events 监视事件也就无效并且 revents返回 0。 revents 是返回参数也就是返回的事件由 Linux 内核设置具体的返回事件。 events 是要监视的事件可监视的事件类型如下所示 /*POLLIN 有数据可以读取。POLLPRI 有紧急的数据需要读取。POLLOUT 可以写数据。POLLERR 指定的文件描述符发生错误。POLLHUP 指定的文件描述符挂起。POLLNVAL 无效的请求。POLLRDNORM 等同于 POLLIN*/ 使用 poll 函数对驱动文件进行读非阻塞访问 void main(void) {int ret;int fd; /* 要监视的文件描述符 */struct pollfd fds;fd open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 *//* 构造结构体 */fds.fd fd;fds.events POLLIN; /* 监视数据是否可以读取 */ret poll(fds, 1, 500); /* 轮询文件是否可操作超时 500ms */if (ret 0) { /* 数据有效 */// 读取数据} else if (ret 0) { /* 超时 */// 处理超时情况} else if (ret 0) { /* 错误 */// 处理错误情况} } ③ epoll 函数 之前的 select 和 poll 函数都会随着监听的 fd 数量的增加出现效率低下的问题而且 poll 函数每次必须遍历所有的描述符来检查就绪的描述符。epoll 是为了处理高并发系统需要同时处理大量的并发连接或请求一般再网络编程中用到 epoll。 应用程序中需要使用 epoll_create 函数创建 epoll 句柄 /* size 填大于0的值即可返回值 epoll句柄如果是-1则创建失败*/ int epoll_create(int size); epoll 句柄创建完成后使用 epoll_ctl 函数添加需要监视的文件描述符和事件 /*epfd 需要操作的epoll句柄op 对epoll句柄进行的操作fd 要监视的文件描述符event 要监视的事件类型返回值 0成功 -1失败并且设置 errno 的值为相应的错误码。*/ int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event); /*op进行的操作可以设置如下EPOLL_CTL_ADD 向 epfd(epoll句柄) 添加文件参数 fd 表示的描述符。EPOLL_CTL_MOD 修改参数 fd 的 event 事件。EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。*/ 这里的 event 是 epoll_event 的结构体指针 struct epoll_event {uint32_t events; /* epoll 事件 */epoll_data_t data; /* 用户数据 */ }; 并且结构体 epoll_event 的 events 成员变量表示要监视的事件可选如下 /*EPOLLIN 有数据可以读取。EPOLLOUT 可以写数据。EPOLLPRI 有紧急的数据需要读取。EPOLLERR 指定的文件描述符发生错误。EPOLLHUP 指定的文件描述符挂起。EPOLLET 设置 epoll 为边沿触发默认触发模式为水平触发。EPOLLONESHOT 一次性的监视当监视完成以后还需要再次监视某个 fd那么就需要将fd 重新添加到 epoll 里面。*/ 上面的事件都可以进行 或 操作也就是说一次可以设置监视多个事件。 以上设置好以后应用程序可以通过 epoll_wati 函数来等待事件的发生 /* epfd 要等待的 epollevents 指向 epoll_event 结构体的数组当有事件发生的时候 Linux 内核会填写 events调用者可以根据 events 判断发生了哪些事件maxevents events 数组大小必须大于 0timeout 超时时间单位为 ms返回值 0超时 -1错误其他值准备就绪的文件描述符数量*/ int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout); 使用 epoll 函数对驱动文件进行读非阻塞访问 void main(void) {int ret;int fd; /* 要监视的文件描述符 */int epoll epoll_create(1); // 创建epoll句柄fd open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 *//* 构造结构体 */struct epoll_event event; // 创建event结构体变量event.events EPOLLIN; // 这里可以 或上其他想监视的事件ret epoll_ctl(epoll, EPOLL_CTL_ADD, fd, event); if (ret 0) { /* 成功 */// 处理成功情况} else if (ret 0) { /* 失败 */ // 失败会自动设置 errno 相应的错误码// 处理失败情况}ret epoll_wait(epoll, event, 4, 500); // 等待事件发生if (ret 0) { /* 超时 */{// 处理超时情况}else if (ret 0) /* 错误 */{// 出现错误情况}else /* 准备就绪的文件描述符数量*/{// 读取数据}} 4. poll 操作函数内核空间 当应用程序调用 select 或 poll 函数对驱动程序进行非阻塞访问的时候驱动程序 file_operations 中的 poll 函数就会执行。 /*filp 要打开的设备文件wait 由应用程序传递进来一般将此参数传递给poll_wait函数返回值 向应用程序返回设备或者资源状态*/ unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait);/*返回的资源状态POLLIN 有数据可以读取。POLLPRI 有紧急的数据需要读取。POLLOUT 可以写数据。POLLERR 指定的文件描述符发生错误。POLLHUP 指定的文件描述符挂起。POLLNVAL 无效的请求。POLLRDNORM 等同于 POLLIN普通数据可读*/ 我们需要在驱动程序的 poll 函数中调用 poll_wait 函数 poll_wait 函数不会引起阻塞只是将应用程序添加到 poll_table 中 /*description 将一个等待队列头添加到轮询表中以便在poll函数中对其进行轮询并在事件发生时唤醒等待的进程filp 设备文件wait_address 添加到 poll_table 中的等待队列头p poll_table就是 file_operations 中 poll 函数的 wait 参数*/ void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p); 二、阻塞 IO 实验 首先拿出上一节的中断按键来加载驱动并输入以下命令 ./keyirqApp /dev/key 测试正常后输入 top 命令查看 keyirqApp 应用程序的 CPU 使用率 可以看出就这一个应用程序就占了 CPU 的 50%原因是我们一直在用 while 循环通过 read 函数读取按键值因此进程会一直运行。最好的办法就是在没有按键事件的时候keyirqApp 应用处于休眠状态当有按键发生 keyirqApp 应用程序才运行这样可以降低 CPU 使用率。 2.1 程序编写 这次是在 13_irq 基础上完成。 cd linux/atk-mpl/Drivers/ mkdir 14_blockio 将 13_irq 文件夹里的 keyirq.c、keyirqApp.c 和 Makefile 复制到 14_blockio并重命名为 blockio。修改 blockio.c #include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/of.h #include linux/of_address.h #include linux/of_gpio.h #include linux/semaphore.h #include linux/of_irq.h #include linux/irq.h #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h#define KEY_CNT 1 /* 设备号个数 */ #define KEY_NAME key /* 名字 *//* 定义按键状态 */ enum key_status {KEY_PRESS 0, /* 按键按下 */ KEY_RELEASE, /* 按键松开 */ KEY_KEEP, /* 按键状态保持 */ };/* key设备结构体 */ struct key_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */int key_gpio; /* key所使用的GPIO编号 */struct timer_list timer; /* 按键值 */int irq_num; /* 中断号 */atomic_t status; /* 按键状态 */ // 这里使用了原子操作wait_queue_head_t r_wait; /* 读等待队列头 */ // wait_queue_head_t是为了兼容老版本 };static struct key_dev key; /* 按键设备 *//* 中断进入定时器定时时间是把按键抖动给延时掉 */ static irqreturn_t key_interrupt(int irq, void *dev_id) // 中断处理函数 {/* 按键防抖处理开启定时器延时15ms */mod_timer(key.timer, jiffies msecs_to_jiffies(15)); // 为什么需要周期性的定时器是因为每当检测到按下一次就需要定时器延时return IRQ_HANDLED; // IRQ_HANDLED是一个预定义的常量表示中断已经得到处理并且处理程序成功执行了必要的操作 }/** description : 初始化按键IOopen函数打开驱动的时候* 初始化按键所使用的GPIO引脚。* param : 无* return : 无*/ static int key_parse_dt(void) {int ret;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点key */key.nd of_find_node_by_path(/key);if(key.nd NULL) {printk(key node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(key.nd, status, str);if(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(key.nd, compatible, str);if(ret 0) {printk(key: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,key)) {printk(key: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到KEY0所使用的KYE编号 */key.key_gpio of_get_named_gpio(key.nd, key-gpio, 0);if(key.key_gpio 0) {printk(cant get key-gpio);return -EINVAL;}/* 5 、获取GPIO对应的中断号 */key.irq_num irq_of_parse_and_map(key.nd, 0);if(!key.irq_num){return -EINVAL;}printk(key-gpio num %d\r\n, key.key_gpio);return 0; }/* 主要进行GPIO和中断的初始化 */ static int key_gpio_init(void) {int ret;unsigned long irq_flags;/* 使用GPIO就要申请GPIO使用权 */ret gpio_request(key.key_gpio, KEY0);if (ret) {printk(KERN_ERR key: Failed to request key-gpio\n);return ret;} /* 将GPIO设置为输入模式 */gpio_direction_input(key.key_gpio);/* 获取设备树中指定的中断触发类型 */irq_flags irq_get_trigger_type(key.irq_num); // 获得定义的中断触发类型if (IRQF_TRIGGER_NONE irq_flags)irq_flags IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/* 申请中断(使用中断必须申请中断) */ret request_irq(key.irq_num, key_interrupt, irq_flags, Key0_IRQ, NULL); // request_irq会默认使能中断所以不需要enable_irq使能中断if (ret) {gpio_free(key.key_gpio);return ret;}// 建议申请成功后先用disbale_irq函数禁止中断等所有工作完成之后再来使能中断return 0; }/* 定时器处理函数 */ static void key_timer_function(struct timer_list *arg) {static int last_val 1; // 保存按键上一次读取到的值int current_val; // 存放当前按键读取的值/* 读取按键值并判断按键当前状态 */current_val gpio_get_value(key.key_gpio);if (0 current_val last_val) // 读取的值为0上次的值为1则是按下{atomic_set(key.status, KEY_PRESS); /* 按下 */wake_up_interruptible(key.r_wait); // 唤醒}else if (1 current_val !last_val){atomic_set(key.status, KEY_RELEASE); /* 松开 */wake_up_interruptible(key.r_wait); // 唤醒}elseatomic_set(key.status, KEY_KEEP); /* 状态保持 */ last_val current_val; }/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/ static int key_open(struct inode *inode, struct file *filp) {return 0; }/** description : 从设备读取数据 * param – filp : 要打开的设备文件(文件描述符)* param – buf : 返回给用户空间的数据缓冲区* param – cnt : 要读取的数据长度* param – offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/ static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt) {int ret;/* 函数会导致当前进程进入睡眠状态直到有其他进程通过调用wake_up_interruptible来唤醒等待队列上的进程 */ret wait_event_interruptible(key.r_wait, KEY_KEEP ! atomic_read(key.status)); // 等待按键状态变化的条件成立从而保证进程只会在按键状态变化时被唤醒if (ret){return ret;}/* 将按键状态信息发送给应用程序 */ret copy_to_user(buf, key.status, sizeof(int));/* 状态重置 */atomic_set(key.status, KEY_KEEP);return ret; }/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/ static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/ static int key_release(struct inode *inode, struct file *filp) {return 0; }/* 设备操作函数 */ static struct file_operations key_fops {.owner THIS_MODULE,.open key_open,.read key_read,.write key_write,.release key_release, };/** description : 驱动入口函数* param : 无* return : 无*/ static int __init mykey_init(void) {int ret;/* 初始化等待队列头 */init_waitqueue_head(key.r_wait);/* 初始化按键状态初始化原子变量*/atomic_set(key.status, KEY_KEEP);/* 设备树解析 */ret key_parse_dt();if(ret)return ret;/* GPIO 中断初始化 */ret key_gpio_init();if(ret)return ret;/* 注册字符设备驱动 *//* 1、创建设备号 */ret alloc_chrdev_region(key.devid, 0, KEY_CNT, KEY_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, KEY_NAME, ret);goto free_gpio;}/* 2、初始化cdev */key.cdev.owner THIS_MODULE;cdev_init(key.cdev, key_fops);/* 3、添加一个cdev */ret cdev_add(key.cdev, key.devid, KEY_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */key.class class_create(THIS_MODULE, KEY_NAME);if (IS_ERR(key.class)) {goto del_cdev;}/* 5、创建设备 */key.device device_create(key.class, NULL, key.devid, NULL, KEY_NAME);if (IS_ERR(key.device)) {goto destroy_class;}/* 6、初始化timer设置定时器处理函数,还未设置周期所有不会激活定时器 */timer_setup(key.timer, key_timer_function, 0);return 0;destroy_class:class_destroy(key.class); del_cdev:cdev_del(key.cdev); del_unregister:unregister_chrdev_region(key.devid, KEY_CNT); free_gpio:free_irq(key.irq_num, NULL);gpio_free(key.key_gpio);return -EIO; }/** description : 驱动出口函数* param : 无* return : 无*/ static void __exit mykey_exit(void) {/* 注销字符设备驱动 */cdev_del(key.cdev);/* 删除cdev */unregister_chrdev_region(key.devid, KEY_CNT); /* 注销设备号 */del_timer_sync(key.timer); /* 删除timer */device_destroy(key.class, key.devid);/*注销设备 */class_destroy(key.class); /* 注销类 */free_irq(key.irq_num, NULL); /* 释放中断 */gpio_free(key.key_gpio); /* 释放IO */ }module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(ALIENTEK); MODULE_INFO(intree, Y); 因为 Linux 驱动处理阻塞 IO 需要用到等待队列而等待队列会使进程进入睡眠 当能引起睡眠的时候一定不能使用自旋锁。 简化版阻塞 IO 流程 在等待队列实现阻塞访问注意两点 ① 将任务或进程加入到等待队列头里代码中是通过 wait_event_interruptible加入到等待队列中 ② 在合适的地方唤醒等待队列一般是在中断处理函数里。 2.2 运行测试 修改 Makefile 文件将 obj-m : keyirq.o 修改为 obj-m : blockio.o。 编译 blockio.c 和 blockioApp.c 文件 make arm-none-linux-gnueabihf-gcc blockioApp.c -o blockioApp 将 blockioApp 和 blockio.ko 文件复制 sudo cp blockioApp blockio.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f 开启开发板输入命令 cd lib/modules/5.4.31/ depmod modprobe blockio.ko 测试 App ./blockioApp /dev/key 输入 top 查看 blockioApp 应用的 CPU 使用率 当我们在按键驱动程序里加入阻塞访问后blockioApp 应用使用 CPU 使用率直线下降。这里的 0% 并不是 0而是 0.0001%使用率太低会导致这种情况。 因为之前用了 这个指在后台运行那么如何关闭呢使用 kill 命令即可。首先使用 ps 查看当前的进程 知道了 blockioApp 进程 PID 为 197使用 kill 命令 kill -9 197 # -9发送强制终止信号 之后就没有 blockioApp。 卸载驱动 rmmod blockio.ko 三、非阻塞 IO 实验 3.1 驱动程序编写 创建 15_noblockio并创建 VScode 工作区并把 blockio.c 文件复制到 15_noblockio 文件夹内并重命名为 noblockio.c。修改 noblock.c 文件 #include linux/types.h #include linux/kernel.h #include linux/delay.h #include linux/ide.h #include linux/init.h #include linux/module.h #include linux/errno.h #include linux/gpio.h #include linux/cdev.h #include linux/device.h #include linux/of.h #include linux/of_address.h #include linux/of_gpio.h #include linux/semaphore.h #include linux/of_irq.h #include linux/irq.h #include linux/wait.h #include linux/poll.h // 新增 poll.h 因为有使用到 poll 函数 #include asm/mach/map.h #include asm/uaccess.h #include asm/io.h#define KEY_CNT 1 /* 设备号个数 */ #define KEY_NAME key /* 名字 *//* 定义按键状态 */ enum key_status {KEY_PRESS 0, // 按键按下KEY_RELEASE, // 按键松开KEY_KEEP, // 按键状态保持 };/* key设备结构体 */ struct key_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */int key_gpio; /* key所使用的GPIO编号 */struct timer_list timer; /* 按键值 */int irq_num; /* 中断号 */atomic_t status; /* 按键状态 */wait_queue_head_t r_wait; /* 读等待队列头 */ };static struct key_dev key; /* 按键设备 */static irqreturn_t key_interrupt(int irq, void *dev_id) {/* 按键防抖处理开启定时器延时15ms */mod_timer(key.timer, jiffies msecs_to_jiffies(15));return IRQ_HANDLED; }/** description : 初始化按键IOopen函数打开驱动的时候* 初始化按键所使用的GPIO引脚。* param : 无* return : 无*/ static int key_parse_dt(void) {int ret;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点key */key.nd of_find_node_by_path(/key);if(key.nd NULL) {printk(key node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(key.nd, status, str);if(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(key.nd, compatible, str);if(ret 0) {printk(key: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,key)) {printk(key: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到KEY0所使用的KYE编号 */key.key_gpio of_get_named_gpio(key.nd, key-gpio, 0);if(key.key_gpio 0) {printk(cant get key-gpio);return -EINVAL;}/* 5 、获取GPIO对应的中断号 */key.irq_num irq_of_parse_and_map(key.nd, 0);if(!key.irq_num){return -EINVAL;}printk(key-gpio num %d\r\n, key.key_gpio);return 0; }static int key_gpio_init(void) {int ret;unsigned long irq_flags;ret gpio_request(key.key_gpio, KEY0);if (ret) {printk(KERN_ERR key: Failed to request key-gpio\n);return ret;} /* 将GPIO设置为输入模式 */gpio_direction_input(key.key_gpio);/* 获取设备树中指定的中断触发类型 */irq_flags irq_get_trigger_type(key.irq_num);if (IRQF_TRIGGER_NONE irq_flags)irq_flags IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/* 申请中断 */ret request_irq(key.irq_num, key_interrupt, irq_flags, Key0_IRQ, NULL);if (ret) {gpio_free(key.key_gpio);return ret;}return 0; }static void key_timer_function(struct timer_list *arg) {static int last_val 1;int current_val;/* 读取按键值并判断按键当前状态 */current_val gpio_get_value(key.key_gpio);if (0 current_val last_val){atomic_set(key.status, KEY_PRESS); // 按下wake_up_interruptible(key.r_wait); // 唤醒r_wait队列头中的所有队列}else if (1 current_val !last_val) {atomic_set(key.status, KEY_RELEASE); // 松开wake_up_interruptible(key.r_wait); // 唤醒r_wait队列头中的所有队列}elseatomic_set(key.status, KEY_KEEP); // 状态保持last_val current_val; }/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/ static int key_open(struct inode *inode, struct file *filp) {return 0; }/** description : 从设备读取数据 * param – filp : 要打开的设备文件(文件描述符)* param – buf : 返回给用户空间的数据缓冲区* param – cnt : 要读取的数据长度* param – offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/ static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt) {int ret;// 判断是否是非阻塞读取访问if (filp-f_flags O_NONBLOCK) { // 非阻塞方式访问if(KEY_KEEP atomic_read(key.status)) // 判断是否按下或松开按键如果没有就返回-EAGAINreturn -EAGAIN;} else { // 阻塞方式访问/* 加入等待队列当有按键按下或松开动作发生时才会被唤醒 */ret wait_event_interruptible(key.r_wait, KEY_KEEP ! atomic_read(key.status));if(ret)return ret;}/* 将按键状态信息发送给应用程序 */ret copy_to_user(buf, key.status, sizeof(int));/* 状态重置 */atomic_set(key.status, KEY_KEEP);return ret; }/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/ static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/ static int key_release(struct inode *inode, struct file *filp) {return 0; }/** description : poll函数用于处理非阻塞访问当用户空间调用 select/poll时候key_poll函数就会执行* param - filp : 要打开的设备文件(文件描述符)* param - wait : 等待列表(poll_table)* return : 设备或者资源状态*/ static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait) {unsigned int mask 0;/* 调用 poll_wait 函数将等待队列头添加到 poll_table 中 */poll_wait(filp, key.r_wait, wait); // file_operations 中 poll 函数的 wait 参数if(KEY_KEEP ! atomic_read(key.status)) // 按键按下或松开动作发生mask POLLIN | POLLRDNORM; // 返回PLLINreturn mask; }/* 设备操作函数 */ static struct file_operations key_fops {.owner THIS_MODULE,.open key_open,.read key_read,.write key_write,.release key_release,.poll key_poll, // };/** description : 驱动入口函数* param : 无* return : 无*/ static int __init mykey_init(void) {int ret;/* 初始化等待队列头 */init_waitqueue_head(key.r_wait);/* 初始化按键状态 */atomic_set(key.status, KEY_KEEP);/* 设备树解析 */ret key_parse_dt();if(ret)return ret;/* GPIO 中断初始化 */ret key_gpio_init();if(ret)return ret;/* 注册字符设备驱动 *//* 1、创建设备号 */ret alloc_chrdev_region(key.devid, 0, KEY_CNT, KEY_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, KEY_NAME, ret);goto free_gpio;}/* 2、初始化cdev */key.cdev.owner THIS_MODULE;cdev_init(key.cdev, key_fops);/* 3、添加一个cdev */ret cdev_add(key.cdev, key.devid, KEY_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */key.class class_create(THIS_MODULE, KEY_NAME);if (IS_ERR(key.class)) {goto del_cdev;}/* 5、创建设备 */key.device device_create(key.class, NULL, key.devid, NULL, KEY_NAME);if (IS_ERR(key.device)) {goto destroy_class;}/* 6、初始化timer设置定时器处理函数,还未设置周期所有不会激活定时器 */timer_setup(key.timer, key_timer_function, 0);return 0;destroy_class:device_destroy(key.class, key.devid); del_cdev:cdev_del(key.cdev); del_unregister:unregister_chrdev_region(key.devid, KEY_CNT); free_gpio:free_irq(key.irq_num, NULL);gpio_free(key.key_gpio);return -EIO; }/** description : 驱动出口函数* param : 无* return : 无*/ static void __exit mykey_exit(void) {/* 注销字符设备驱动 */cdev_del(key.cdev);/* 删除cdev */unregister_chrdev_region(key.devid, KEY_CNT); /* 注销设备号 */del_timer_sync(key.timer); /* 删除timer */device_destroy(key.class, key.devid);/*注销设备 */class_destroy(key.class); /* 注销类 */free_irq(key.irq_num, NULL); /* 释放中断 */gpio_free(key.key_gpio); /* 释放IO */ }module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(ALIENTEK); MODULE_INFO(intree, Y); 编写 noblockApp.c #include stdio.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include stdlib.h #include string.h #include poll.h/** description : main主程序* param – argc : argv数组元素个数* param – argv : 具体参数* return : 0 成功;其他 失败*/ int main(int argc, char *argv[]) {fd_set readfds; /* 读操作文件描述符集 */int key_val;int fd;int ret;/* 判断传参个数是否正确 */if(2 ! argc) {printf(Usage:\n\t./keyApp /dev/key\n);return -1;}/* 打开设备 */fd open(argv[1], O_RDONLY | O_NONBLOCK); // O_NONBLOCK非阻塞模式if(0 fd) {printf(ERROR: %s file open failed!\n, argv[1]);return -1;}FD_ZERO(readfds); // 清零FD_SET(fd, readfds); // 置一/* 循环轮训读取按键数据 */while (1) {ret select(fd 1, readfds, NULL, NULL, NULL);switch (ret) {case 0: // 超时/* 用户自定义超时处理 */{printf(Timeout!\n);}break;case -1: // 错误/* 用户自定义错误处理 */{printf(Something is wrong\n);}break;default:if(FD_ISSET(fd, readfds)) {read(fd, key_val, sizeof(int));if (0 key_val)printf(Key Press\n);else if (1 key_val)printf(Key Release\n);}break;}}/* 关闭设备 */close(fd);return 0; } 3.2 运行测试 编写 Makefile KERNELDIR : /home/alientek/linux/atk-mp1/linux/my_linux/linux-5.4.31 CURRENT_PATH : $(shell pwd)obj-m : noblockio.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean 编译 noblock.c 和 noblockApp.c make arm-none-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp 将 noblockApp 和 noblock.ko 复制  sudo cp noblockioApp noblockio.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f 开启开发板输入以下命令 cd lib/modules/5.4.31/ depmod modprobe noblockio.ko 测试驱动 ./noblockioApp /dev/key 之后输入 top 就可以看到 这样就完成了非阻塞 IO 实验。 如果直接卸载驱动的话会出现 这个是因为没有 kill 指定后台kill -9 228。 之后再卸载驱动rmmod noblockioApp.ko 总结 概念 阻塞和非阻塞一个是在内核里面一个是在内核和用户空间反复跳。 他们的概念需要知道。 阻塞当一个任务或线程执行某个操作时如果该操作无法立即完成任务或线程将会被阻塞暂停执行。阻塞式会一直占用 CPU直到操作完成这里面有 等待队列头等待队列项等待唤醒和等待事件比较重要。 非阻塞当一个任务或线程执行某个操作时如果该操作无法立即完成任务或线程不会被阻塞而是立即返回继续执行后续的任务或代码。非阻塞式调用不会一直占用 CPU 资源任务或线程可以在等待操作完成时去处理其他任务或代码。这里面有 三个函数 slect、poll、epoll。使用方式得知道。
http://www.zqtcl.cn/news/913169/

相关文章:

  • 辽宁省建设工程招标协会网站自建网站主题及策划
  • 石材做网站google建网站
  • 装配式建筑信息平台无忧seo博客
  • 做淘客网站用备案网络推广预算方案
  • 网站建设需不需要招标好网站欣赏
  • 怎样创建网站的代码此网站域名即将过期
  • 网页转向网站jquery图片效果网站
  • 山东定制网页建站wordpress是是什么技术
  • 无锡免费网站制作手游网页版
  • 东莞 网站建设 定制水寻找常州微信网站建设
  • 在门户网站做推广网站开发需要20万
  • 网站做电商销售需要注册吗上海的公司地址
  • 给网站做选题计算机网络技术电商网站建设与运营方向
  • 网站如何做熊掌号并绑定wordpress pdf
  • wordpress页面构建器中文文山seo公司
  • 凡科免费做网站蜂箱尺寸与制作图片
  • 完全不收费的聊天软件班级优化大师下载安装app
  • 合肥网站改版360免费建站永久免费
  • 商业网站建设案例课程 下载工信部企业网站认证
  • 泉州网站设计哪家公司好沈阳seo代理计费
  • 做景观素材有哪几个网站国内建网站费用
  • 驻马店重点项目建设网站wordpress常规选项
  • 网站开发 英文网站策划建设阶段的推广
  • 建立网站一般多少钱wordpress评论跳过验证
  • 南京每月做社保明细在哪个网站查看设计作品的网站软件
  • html怎么做网站如何在腾讯云上网站建设
  • 网站建设怎么链接表格手机做外贸有什么好的网站
  • 深圳开发网站建设哪家好外贸网络营销培训
  • 广州智迅网络做网站免费下载ps素材网站
  • 什么网站时候做伪静态开发软件定制