Linux字符设备驱动-KEY-阻塞IO、非阻塞IO、信号驱动IO

1.概述

实现了按键的字符驱动,支持在应用层使用open、read、poll、select及signal函数,支持阻塞和非阻塞IO,支持异步通知IO。可以用test.c进行测试,测试命令为./test -a <b/nb/poll/select/signal>b表示阻塞读,nb表示非阻塞读,poll表示使用poll函数,select表示使用select函数,signal表示使用信号。测试结果会输出按键按下和松开的次数。

2.中断

2.1.申请中断和释放中断

对于Linux内核来说,中断是一种资源,由内核统一管理。使用中断之前必须向内核申请,使用完毕后必须释放,把资源规划给内核。驱动使用request_irq申请中断,返回值为0表示申请成功,为负值时表示错误码,使用free_irq释放中断。devm_request_irq申请的资源不需要显示的释放。
irq为要申请的虚拟中断号,handler为中断处理函数(中断上半部分)指针,irqflags与中断管理相关的位掩码,可以指定中断触发方式及处理方式,devname传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者,dev_id用于共享的中断信号线,驱动程序可使用它来识别那个设备产生了中断,类似于struct file中的private_data指针。
由于设备上的中断资源有限,建议在设备打开时申请中断资源,设备关闭时释放中断资源,不建议在模块初始化的时候申请中断。

	include <linux/interrupt.h>
	// 注册中断上半部分处理函数
	// irq-软件(虚拟)中断号,不是硬件中断号。虚拟中断号是内核根据硬件中断号进行分配的,由内核管理,可通过
	//     irq_of_parse_and_map读取设备树中的硬件中断信息,然后返回虚拟中断号
	// handler-中断处理函数,执行时间尽可能短,不能睡眠
	// flags-中断标志
	// name-中断名称
	// dev-传递给中断处理函数handler的参数
	int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
	// 释放注册的中断上半部分处理函数
	void free_irq(unsigned int irq, void *dev_id)
	// 注册中断上半部分处理函数,驱动卸载时可自动释放注册的中断上半部分处理函数
	// dev-设备驱动的设备结构体
	int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, 
						unsigned long irqflags, const char *devname, void *dev_id)   
	// 将设备树中的硬件中断信息映射位虚拟中断号
	// dev-为设备树中的设备节点
	// index-为中断属性的索引号
	unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
	#define IMX_GPIO_NR(bank, nr)	(((bank) - 1) * 32 + (nr))
	#include <linux/gpio.h> 
	// 获取gpio的虚拟中断号
	// gpio-根据引脚所在的gpio back和pin转换而来,不同的平台转转换方法不一样,imx的采用IMX_GPIO_NR宏进行转换。
	int gpio_to_irq(unsigned int gpio)

中断处理方式标志:

	#define IRQF_SHARED		    0x00000080  // 共享中断
	#define IRQF_PROBE_SHARED	0x00000100
	#define __IRQF_TIMER		0x00000200
	#define IRQF_PERCPU		    0x00000400
	#define IRQF_NOBALANCING	0x00000800  // 不受中断平衡影响
	#define IRQF_IRQPOLL		0x00001000
	#define IRQF_ONESHOT		0x00002000  // 直到中断处理线程开始执行时才取消屏蔽中断
	#define IRQF_NO_SUSPEND		0x00004000
	#define IRQF_FORCE_RESUME	0x00008000
	#define IRQF_NO_THREAD		0x00010000
	#define IRQF_EARLY_RESUME	0x00020000
	#define IRQF_COND_SUSPEND	0x00040000
	// 定时器中断
	#define IRQF_TIMER	(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

中断触发方式标志:

	#define IRQF_TRIGGER_NONE	0x00000000   // 不指定触发方式
	#define IRQF_TRIGGER_RISING	0x00000001   // 上升沿
	#define IRQF_TRIGGER_FALLING 0x00000002  // 下降沿
	#define IRQF_TRIGGER_HIGH	0x00000004   // 高电平
	#define IRQF_TRIGGER_LOW	0x00000008   // 低电平

中断处理函数的定义为typedef irqreturn_t (*irq_handler_t)(int, void *),第一个参数为中断号,第二个参数为调用request_irq申请中断时的dev。返回值有如下三种:

	include <linux/irqreturn.h>
	enum irqreturn {
		IRQ_NONE		= (0 << 0),    // 非本设备中断
		IRQ_HANDLED		= (1 << 0),    // 中断处理完毕
		IRQ_WAKE_THREAD	= (1 << 1),    // 唤醒中断处理线程,用于处理中断后半部分
	};

申请中断时如果需要为设备建立一个中断处理线程,需要使用request_threaded_irq或者devm_request_threaded_irq函数,handler为主处理函数,位于中断上半部分,thread_fn为中断处理线程函数,位于中断上半部分。如果中断处理函数返回IRQ_WAKE_THREAD,则会唤醒中断线程。假如handler为空,则调用默认的主处理函数irq_default_primary_handler,该函数返回IRQ_WAKE_THREAD

	include <linux/interrupt.h>
	int request_threaded_irq(unsigned int irq, irq_handler_t handler,  irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev);
	int devm_request_threaded_irq(struct device *dev, unsigned int irq,irq_handler_t handler, irq_handler_t thread_fn,unsigned long irqflags, const char *devname,void *dev_id);
2.2.禁用中断

使用disable_irqdisable_irq_nosync禁用单个中断,前者在禁止中断之前如有中断发生则会等待中断完成,后者禁止中断并立即返回,使用enable_irq使能中断。disable_irqdisable_irq_nosync可嵌套使用,但必须和enable_irq配对。

	include <linux/interrupt.h>
	void disable_irq(unsigned int irq)
	void disable_irq_nosync(unsigned int irq)
	void enable_irq(unsigned int irq)
2.3.禁用本地CPU中断
	include <linux/irqflags.h>
	// 禁止当前处理器的所有中断,并保存标志
	#define local_irq_save(flags) do {raw_local_irq_save(flags);} while (0)
	// 使能当前处理器的所有中断,并恢复标志
	#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
	// 禁止当前处理器的所有中断
	#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
	// 使能当前处理器的所有中断
	#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)

3.内核时钟中断

内核正常运行,需要系统时钟周期性的产生中断,产生中断的频率由宏定义HZ决定,大多数平台定义范围为50-1200,ARM平台常见定义为100.如想改变系统时钟中断发生的频率,可通过修改HZ值,但修改后要重新编译内核及所有模块。
每当时钟中断发生时,内核内部计数器加一,计数器在系统引导的时候初始化为0,因此它的值为自启动以来的时钟滴答数,此计数器为jiffies_64,为64位的变量。但在驱动程序中通常访问的是jiffies,如unsigned long为64位,那么jiffiesjiffies_64相同,如unsigned long为32位,那么jiffiesjiffies_64的低32位,通常情况下访问jiffies,速度会快一点。

	include <linux/jiffies.h>
	u64 __jiffy_data jiffies_64;
	unsigned long volatile __jiffy_data jiffies;
    // 时间和jiffies的转化关系可用下面的函数实现。
	// jiffies值转换为毫妙数
	unsigned int jiffies_to_msecs(const unsigned long j);
	// jiffies值转换为微妙数
	unsigned int jiffies_to_usecs(const unsigned long j);
	// 毫妙数转换为jiffies
	unsigned long msecs_to_jiffies(const unsigned int m);
	// 微妙数转换为jiffies
	unsigned long usecs_to_jiffies(const unsigned int u);

4.内核定时器

如果需要在将来的某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内核定时器。内核定时器可用来在未来的某个特定时间点(基于时钟滴答)调度执行某个函数,从而完成特定的任务。定时器基于软中断实现,定时器到期执行的函数需要满足一下几点:
(1)不允许访问用户空间。因为没有进程上下文,无法将任何特定进程与用户空间关联起来
(2)current指针在原子模式下没有任何意义,也是不可用的,因为相关代码和被中断的进程没有任何关联。
(3)不能执行休眠或调度。原子代码不可以调用schedule或者wait_event,也不能调用任何可能引起休眠的函数,如调用kmalloc(..., GFP_KERNEL)、使用信号量等。
在SMP系统中,定时器函数会由注册它的CPU执行,不会调度到其他CPU上执行,这样可以尽可能获得缓存的局域性。内核为驱动程序提供了一组用来声明、注册和删除内核定时器的函数。

	include <linux/timer.h>
	struct timer_list {
		/* All fields that change during normal runtime grouped to the same cacheline */
		unsigned long expires;  // 定时器到期的jiffies值
		void (*function)(unsigned long);  // 定时器到期执行的函数
		unsigned long data;  // 定时器到期执行函数的参数
	};
	// 定义并初始化定时器
	#define DEFINE_TIMER(_name, _function, _expires, _data)	struct timer_list _name = TIMER_INITIALIZER(_function, _expires, _data)  
	// 初始化定时器
	#define init_timer(timer)  __init_timer((timer), 0)
	// 注册定时器
	void add_timer(struct timer_list *timer);
	// 删除定时器
	int del_timer(struct timer_list * timer);
	// 更新定时器的到时时间
	int mod_timer(struct timer_list *timer, unsigned long expires);

5.阻塞型I/O

5.1.休眠

当一个进程被置入休眠状态时,它会被标记为一种特殊状态并从调度器的运行队列中移走。直到某些情乱下修改了这个状态,进程才会在任意CPU上调度,也即运行该进程。Linux设备驱动程序让一个进程进入休眠状态很容易,但以安全的方式休眠,需要记住两条规则:
(1)永远不要在原子上下文中进入休眠,原子上下文指在执行多个步骤时,不能有任何的并发访问。这意味着,不能拥有自旋锁、seqlock、或者RCU锁时休眠。如果我们已经禁止了中断,也不能休眠。在拥有信号量休眠时是合法的,但必须确保有拥有信号量的并不会阻塞最终唤醒我们自己的那个进程。
(2)当被唤醒时,永远无法知道休眠期间都发生了什么,通常也无法知道是否还有其他进程在同一事件上休眠,这个进程可能会在我们之前被唤醒并将我们等待的资源拿走。这样,我们对唤醒之后的状态不能做任何假设,因此必须检查以确保我们等待的条件真正为真。

5.2.等待队列

Linux使用等待队列管理休眠的进程,一个等待队列通过一个“等待队列头”来管理。

    include <linux/wait.h>
    // 等待队列头类型
    typedef struct __wait_queue_head wait_queue_head_t;
    // 定义一个等待队列头并初始化
    #define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    // 初始化一个等待队列头,q为等待队列头的指针
    init_waitqueue_head(q)	
    // 定义一个等待队列项并初始化,tsk表示属于那个进程,一般直接设置为current
    DECLARE_WAITQUEUE(name, tsk)
    // 向等待队列头添加等待队列项
    void add_wait_queue(wait_queue_head_t* q, wait_queue_t* wait)
    // 从等待队列头中移除等待队列项
    void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
5.3.休眠方法

Linux内核中最简单的休眠方法是使用wait_event宏,在实现休眠的同时,也检查进程等待的条件。wq为等待队列头,值传递,condition为布尔表达式,表达式结果为假继续休眠,该条件可能会被多次求值,timeout为超时时间。

	include <linux/wait.h>
	// 不可中断休眠
	#define wait_event(wq, condition)
	// 可中断休眠(可被信号中断),返回值非0时表示被某个信号中断
	#define wait_event_interruptible(wq, condition)
	// 有限时间不可中断休眠,超时时间到期不管条件真假都返回,此时返回0
	#define wait_event_timeout(wq, condition, timeout)
	// 有限时间可中断休眠,超时时间到期不管条件真假都返回,此时返回0,
	// 返回值非0时表示被某个信号中断
	#define wait_event_interruptible_timeout(wq, condition, timeout)
5.4.唤醒方法

进程可以休眠,也必须可以唤醒,用来唤醒的基本函数是wake_upx为等待队列头的指针。

	// 唤醒给定等待队列上的所有非独占休眠进程
	#define wake_up(x)
	// 唤醒给定等待队列上的可中断的非独占休眠进程
	#define wake_up_interruptible(x)
5.5.阻塞和非阻塞操作

Linux I/O操作默认是阻塞的。如果应用想非阻塞调用,必须显出的指出,可在open设备的时候通过O_NONBLOCK标志决定,此标志在<linux/fcntl.h>中定义,自动在<linux/fs.h>中包含,当使用了O_NONBLOCK标志,系统调用会立刻返回,不管调用是否成功。驱动程序中,可通过检测O_NONBLOCK标志,确定应用调用的是阻塞IO还是非阻塞IO,然后采取不同的措施。
使用非阻塞的I/O的应用程序也经常使用pollselectepoll系统调用。pollselectepoll的功能本质上是一致的,都允许进程对一个或者多个打开的文件做非阻塞的读取或写入,这些调用本身会阻塞,直到给定的文件描述符集合中的任何一个可读取或者可写入。上述系统调用需要设备驱动程序支持,所有三个系统调用均通过驱动程序的poll方法提供,原型如下,位于struct file_operations结构体中。

	include <linux/fs.h>
	unsigned int (*poll) (struct file *, struct poll_table_struct *);    

poll函数分为两步处理:
(1)在一个或多个可指示poll状态变化的等待队列上调用poll_wait。如果当前没有文件描述符可用来执行I/O,则内核将使进程在传递该系统调用的所有文件描述符对应的等待队列上等待。
(2)返回一个用来描述操作是否可以立即无阻塞执行的位验码。
驱动程序中,需要调用poll_wait函数,将等待队列添加到poll_table结构中。filp 为文件指针,wait_address为等待队列头指针,ppoll_table结构指针。

	include <linux/poll.h>
	void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
	typedef struct poll_table_struct {
		poll_queue_proc _qproc;
		unsigned long _key;
	} poll_table; // struct poll_table_struct与poll_table一样
	#define POLLIN	     0x0001 // 设备可以无阻塞的读取
	#define POLLRDNORM	 0x0002 // 数据已经就绪,可以读取,可与POLLIN一起使用
	#define POLLOUT	     0x0004 // 设备可以无阻塞低写入
	#define POLLERR	     0x0008 // 设备发生了错误
	#define POLLHUP	     0x0010 // 读取设备的进程到达了文件尾
	#define POLLWRNORM   0x0020 // 数据已经就绪,可以写入,可与POLLOUT一起使用

6.信号驱动

readwritepollselectepoll等系统调用属于同步系统调用,都是应用主动调用系统调用进入内核,轮询资源是否可用。在有些情况下,这种轮询方式并不合适。异步通知不需要应用主动轮询,只要做一些设置,在资源可用时,应用会收到信号。

	include <linux/fs.h>
	// struct file_operations结构体成员,需要驱动实现此函数
	int (*fasync) (int, struct file *, int);
	// 当应用修改打开文件的FASYNC标志,就会调用此函数从相关的进程列表中增加或删除文件
	// 直白的说驱动程序调用此函数记录信号发给谁
	int fasync_helper(int, struct file *, int, struct fasync_struct **);
	// 发出信号,通知所有相关进程,驱动程序一般在中断中调用此函数
	void kill_fasync(struct fasync_struct **, int, int);

7.字符设备驱动源码

	/*===========================my_key.h================================*/
	#include <linux/cdev.h>
	#include <linux/device.h>
	#include <linux/mutex.h>
	#include <linux/timer.h>
	#include <linux/wait.h>
	#include <linux/fs.h>
	
	// GPIO5寄存器地址定义
	#define GPIO5_DR  		(0x020AC000)  // 数据寄存器
	#define GPIO5_DRIR  	(0x020AC004)  // 方向寄存器,0为输入,1为输出
	#define GPIO5_PSR  		(0x020AC008)  // 状态寄存器
	#define GPIO5_ICR1  	(0x020AC00C)  // 中断配制寄存器1
	#define GPIO5_ICR2  	(0x020AC010)  // 中断配制寄存器2
	#define GPIO5_ICRIMR    (0x020AC014)  // 中断掩码寄存器
	#define GPIO5_ICRISR    (0x020AC018)  // 中断状态寄存器
	#define GPIO5_EDGE_SEL  (0x020AC01C)  // 边缘选择寄存器,用以配制双边沿触发中断
	#define GPIO5_1_MASK  (1 << 1)  // GPIO5_1掩码(SNVS_TAMPER1)
	// GPIO5_1引脚复用控制寄存器
	#define IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1  (0x2290000 + 0xC)
	// GPIO5_1引脚复用模式为ATL5					
	#define ATL5  (5)
	#define GPIO5_1_MUX_MODE  ATL5
	#define GPIO5_1_MUX_MODE_MASK (0xF)
	// GPIO5_1引脚控制寄存器,配制电气属性,如上下拉等
	#define IOMUXC_SNVS_SW_PAD_CTL_PAD_SNVS_TAMPER1  (0x2290000 + 0x50)
	// GPIO5时钟控制寄存器
	#define CCM_CCGR1  (0x20C4000 + 0x6C)
	#define GPIO5_CLK_MASK  (3 << 30)
	// imx平台gpio转化为整数打宏定义,在gpio_to_irq用到
	#define IMX_GPIO_NR(bank, nr)	(((bank) - 1) * 32 + (nr))
	
	// 添加__iomem标记,编译器会检查此地址,确保在io空间
	struct key_ctl_pin
	{
		unsigned int __iomem* dr_pin;
		unsigned int __iomem* drir_pin;
		unsigned int __iomem* edge_sel_pin;
		unsigned int __iomem* mux_pin;
		unsigned int __iomem* ctl_pin;
		unsigned int __iomem* clk_pin;
	};
	
	// 设备结构体
	struct my_key_dev {
		struct cdev cdev;           // 字符设备结构体
	    struct key_ctl_pin pin;     // key 寄存器映射
		dev_t devno;                // 设备号
		struct class* my_key_class;
		struct device* my_key_device;
	    struct mutex mutex;         // 用于同步的互斥体
		volatile unsigned int key_push_cnt;  // 按键按下的次数
		struct timer_list timer;    // 定时器
		wait_queue_head_t wq;       // 等待队列头
		struct fasync_struct* fasync; // 异步通知结构体
		unsigned int irq;           // 虚拟中断号
		volatile int ev_press;      // 按键按下标志
		volatile char key_val;      // 按键值
	};
	/*===========================my_key.c================================*/
	#include <linux/init.h>
	#include <linux/module.h>
	#include <linux/kernel.h>
	#include <asm/uaccess.h>
	#include <linux/slab.h>
	#include <linux/stat.h>
	#include <linux/sysfs.h>
	#include <linux/interrupt.h>
	#include <linux/jiffies.h>
	#include <linux/sched.h>  
	#include <asm/io.h>
	#include <linux/gpio.h> 
	#include <linux/poll.h>
	#include "my_key.h"
	
	static struct my_key_dev* my_key = NULL;
	/******************************读取数据函数**********************************/
	static ssize_t my_key_read(struct file* filp, char __user* buf, size_t size, loff_t* ppos)
	{
		struct my_key_dev* dev = filp->private_data;
		int ret;
		char val;
		if (size != sizeof(val)) return -EINVAL; 
	
		// 如果是非阻塞打开,判断是否有按键按下,有则尝试读取按键值,没有则直接返回错误
		if (filp->f_flags & O_NONBLOCK) {
			if (1 != dev->ev_press)
				return -EAGAIN;
		} else {
			ret = wait_event_interruptible(dev->wq, dev->ev_press); // 阻塞打开,等待被唤醒
			if (signal_pending(current))  // 判断是否是信号引起的唤醒
				ret = -ERESTARTSYS;
			if (0 != ret) return ret;
		}
		val = dev->key_val;
		ret = copy_to_user(buf, &val, sizeof(val)); 
		dev->ev_press = 0;
		if (0 != ret) return -EFAULT;
		return sizeof(val);
	}
	/*********************按键中断服务函数,在中断的上半段执行***********************/
	irqreturn_t my_key_irq_handler(int irq, void* dev)
	{
		struct my_key_dev* key = (struct my_key_dev*)dev;
		mod_timer(&key->timer, jiffies + msecs_to_jiffies(10)); // 定时器延时10毫秒
		return IRQ_RETVAL(IRQ_HANDLED);
	}
	/******************************定时器到期执行函数****************************/
	void timer_fun(unsigned long data)
	{
		int val = 0;
		struct my_key_dev* dev = (struct my_key_dev*)data;
		val = ioread32(dev->pin.dr_pin);
		val &= GPIO5_1_MASK;
		if (val == GPIO5_1_MASK) 
			dev->key_val = 1;  // 高电平,按键按下
		else
			dev->key_val = 0;  // 低电平,按键松开
		dev->ev_press = 1;
		if (0 != waitqueue_active(&dev->wq))  // 检查是否有进程在等待队列上休眠
			wake_up_interruptible(&dev->wq);  // 唤醒休眠的进程
		if (NULL != dev->fasync)  			  // 检查信号列表中是否进程在等待信号
			kill_fasync (&dev->fasync, SIGIO, POLL_IN);  // 发信号
		dev->key_push_cnt++;
	}
	/*************************初始化按键相关寄存器***********************/
	static void reg_set(struct my_key_dev* dev)
	{
		unsigned int val = 0;
		val = ioread32(dev->pin.mux_pin);
		val &= GPIO5_1_MUX_MODE_MASK;
		val |= GPIO5_1_MUX_MODE;
		iowrite32(val, dev->pin.mux_pin);  // 复用为GPIO5_1
	
		val = ioread32(dev->pin.drir_pin);
		val &= GPIO5_1_MASK;
		iowrite32(val, dev->pin.drir_pin);  // 设置为输入
	
		val = ioread32(dev->pin.clk_pin);
		val |= GPIO5_CLK_MASK;
		iowrite32(val, dev->pin.clk_pin);  // 开启GPIO5的时钟
	}
	/*****************打开设备函数,只用于一个进程或者线程打开********************/
	static int my_key_open(struct inode* inode, struct file* filp)
	{
		unsigned int ret = 0;
		struct my_key_dev* dev = container_of(inode->i_cdev, struct my_key_dev, cdev); 
		// 如果是非阻塞打开,尝试获取互斥体,获取失败直接返回,获取成功继续执行
		if (filp->f_flags & O_NONBLOCK) {
			// mutex_trylock和down_trylock的返回值意义相反
			if (!mutex_trylock(&dev->mutex))
				return -EBUSY;
		}
		else mutex_lock(&dev->mutex);
	 
		reg_set(dev);
	
		// 根据GPIO引脚申请虚拟中断号,IMX_GPIO_NR将GPIO5_1转换成一个32位整数
		dev->irq = gpio_to_irq(IMX_GPIO_NR(5,1));
		// 申请中断,双边沿触发,需要注意的是dev->irq为虚拟中断号,不是硬件中断号
		// 由于GPIO5 0-15引脚共享硬件中断号74,且系统已经使用了GPIO5_3作为心跳led的中断
		// 因此这里还要设置成共享中断
		ret = request_irq(dev->irq, my_key_irq_handler, IRQF_TRIGGER_RISING |   \
					IRQF_TRIGGER_FALLING | IRQF_SHARED, "my_key", dev);
		if (0 != ret) {
			printk(KERN_ERR "request_irq error\n");
			return -EAGAIN;
		}
		filp->private_data = dev;
		printk(KERN_INFO "my_key module open OK\n");
		return 0;
	}
	/***********************驱动实现的poll函数,用于轮询************************/
	static unsigned int my_key_poll(struct file* filep, poll_table* wait)
	{
		unsigned int mask = 0;
		struct my_key_dev* dev = filep->private_data;
		poll_wait(filep, &dev->wq, wait);
		if (1 == dev->ev_press)  // 如有按键按下,则表示有数据读取,返回POLLIN和POLLRDNORM标志
			mask = (POLLIN | POLLRDNORM);
		return mask;
	}
	/*******************驱动实现的fasync函数,实现异步通知功能**********************/
	static int my_key_fasync(int fd, struct file* filep, int on)
	{
		struct my_key_dev* dev = filep->private_data;
		return fasync_helper(fd, filep, on, &dev->fasync);
	}
	/*******************释放设备的函数,应用调用close时调用此函数******************/
	static int my_key_release(struct inode* inode, struct file* filep)
	{
		struct my_key_dev* dev = container_of(inode->i_cdev, struct my_key_dev, cdev); 
		free_irq(dev->irq, dev);  // 释放中断
		my_key_fasync(-1, filep, 0);  // 从异步通知列表中删除filep
		mutex_unlock(&dev->mutex);  // 关闭设备时释放互斥体
		return 0;
	}
	// 文件操作函数结构体
	static const struct file_operations my_key_fops = {
		.owner = THIS_MODULE,
		.open = my_key_open,
		.read = my_key_read,
		.poll = my_key_poll,
		.fasync = my_key_fasync,
		.release = my_key_release,
	};
	/************************映射控制三个led灯所需的寄存器***************************/
	static int key_ioremap(struct my_key_dev* dev)
	{
		int ret = 0;
		// 映射GPIO数据寄存器和方向控制寄存器
		dev->pin.dr_pin = ioremap(GPIO5_DR, 4 * 8);
		dev->pin.drir_pin = dev->pin.dr_pin + 1;
		dev->pin.edge_sel_pin = dev->pin.dr_pin + 7;
		// 映射引脚复用控制寄存器和引脚控制寄存器
		dev->pin.mux_pin = ioremap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1, 4);
		dev->pin.ctl_pin = ioremap(IOMUXC_SNVS_SW_PAD_CTL_PAD_SNVS_TAMPER1, 4);
		// 映射时钟控制寄存器
		dev->pin.clk_pin = ioremap(CCM_CCGR1, 4);
		if ((NULL == dev->pin.dr_pin) || (NULL == dev->pin.mux_pin) || 
		    (NULL == dev->pin.ctl_pin) || (NULL == dev->pin.clk_pin)) {
			ret = -1;
			printk(KERN_ERR "key_ioremap() failed\n"); 
		}
		return ret;	
	}
	/********************注销已映射的key控制寄存器************************/
	static void key_iounmap(struct my_key_dev* dev)
	{
		iounmap(dev->pin.dr_pin);
		iounmap(dev->pin.mux_pin);
		iounmap(dev->pin.ctl_pin);
		iounmap(dev->pin.clk_pin);
	}
	/*******************初始化和注册cdev结构体****************************/
	static int set_up_my_key_cdev(struct my_key_dev* dev, int cnt)
	{
		int err;
		cdev_init(&dev->cdev, &my_key_fops);
		dev->cdev.owner = THIS_MODULE;
		err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
		if (err < 0)
			printk(KERN_ERR "adding my_key cdev %d error, errno %d\n", cnt, err);
		return err;
	}
	/*******************************定义设备的属性********************************/
	static ssize_t key_show(struct device* dev, struct device_attribute* attr,char* buf)
	{
		return sprintf(buf, "pressed button count %u\n", my_key->key_push_cnt);
	} 
	// 设备属性文件名为key,属性结构体名称为dev_attr_key,类型为struct device_attribute
	// 第二个模式参数要和show store函数匹配,如模式可读可写,则show store函数必须都要提供
	static DEVICE_ATTR(key, S_IRUSR | S_IRGRP | S_IROTH, key_show, NULL);
	
	/***************************模块初始化**************************************/
	static int __init my_key_init(void)
	{
		int ret = 0;
		dev_t devno = 0;
		// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配的设备号
		ret = alloc_chrdev_region(&devno, 0, 1, "my_key");
		if (ret < 0) {
			printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
			return ret;	
		}
		// 分配设备结构体内存并将分配的内存清0
		my_key = kzalloc(sizeof(struct my_key_dev), GFP_KERNEL);
		if (NULL == my_key) {
			ret = -ENOMEM;
			printk(KERN_ERR "kzalloc() failed %d\n", ret);
			goto unreg_chrdev;
		}
		my_key->devno = devno;
		my_key->ev_press = 0;
		my_key->key_val = 0;
		init_timer(&my_key->timer);  // 初始化定时器
		my_key->timer.function = timer_fun;
		my_key->timer.data = (unsigned long)my_key;
		add_timer(&my_key->timer);   // 注册定时器
		init_waitqueue_head(&my_key->wq);  // 初始化等待队列头
	
		// 使用ioremap映射控制key所需的寄存器
		if (0 != key_ioremap(my_key)) { ret = -ENOMEM; goto unreg_chrdev; }
	
		ret = set_up_my_key_cdev(my_key, 1);
		if (ret < 0) goto iounmap_key;
	
		// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
		my_key->my_key_class = class_create(THIS_MODULE, "my_key_class");
		if (IS_ERR(my_key->my_key_class)) {
			ret = PTR_ERR(my_key->my_key_class);
			printk(KERN_ERR "class_create() failed %d\n", ret);
			goto del_cdev;
		}
		my_key->my_key_device = device_create(my_key->my_key_class, NULL, devno, 
								NULL,"my_key");
		if (IS_ERR(my_key->my_key_device)) {
			ret = PTR_ERR(my_key->my_key_device);
			printk(KERN_ERR "device_create() failed %d\n", ret);
			goto clean_class;
		}
		// 使用device_create_file创建属性文件,一次只能创建一个
		// 使用device_remove_file移除属性文件
		ret = device_create_file(my_key->my_key_device, &dev_attr_key);
		if (ret != 0) goto clean_device;
	
		// 初始化互斥体
		mutex_init(&my_key->mutex);
		printk(KERN_INFO "my_key module init OK, major %u, minor %u\n", 
						MAJOR(devno), MINOR(devno));
		return 0;
	clean_device:
		device_destroy(my_key->my_key_class, devno);
	clean_class: 
		class_destroy(my_key->my_key_class);
	del_cdev:
		cdev_del(&my_key->cdev);
	iounmap_key:	
		key_iounmap(my_key);
		kfree(my_key);
		my_key = NULL;
	unreg_chrdev:
		unregister_chrdev_region(devno, 1);
	    return ret;
	}
	/********************模块注销************************/
	static void __exit my_key_exit(void)
	{
		device_remove_file(my_key->my_key_device, &dev_attr_key);
		device_destroy(my_key->my_key_class, my_key->devno);
		class_destroy(my_key->my_key_class);
		cdev_del(&my_key->cdev);
		del_timer(&my_key->timer);   // 删除定时器
		key_iounmap(my_key);
		unregister_chrdev_region(my_key->devno, 1);
		kfree(my_key);
		my_key = NULL;
		printk(KERN_INFO "my_key module exit\n");
	}
	
	module_init(my_key_init);
	module_exit(my_key_exit);
	
	MODULE_LICENSE("GPL");
	MODULE_AUTHOR("liyang.plus@foxmail.com");
	MODULE_VERSION("v1.00");

8.测试程序源码

	#include <stdio.h>
	#include <unistd.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <string.h>
	#include <fcntl.h>
	#include <poll.h>
	#include <sys/select.h>
	#include <signal.h>
	#define PATH "/dev/my_key"
	#define OPT_STRING    ":a:"   // 选项字符串
	static void print_usage()
	{
	    printf("Usage:    ./test -a <b/nb/poll/select/signal>\n"
	           "-a:       running mode\n"
	           "b:        block\n"
	           "nb:       non-block\n" 
	           "poll:     poll \n"
	           "select:   select\n"
	           "signal:   asynchronous notification\n");
	}
	
	extern char* optarg;  // 指向参数值
	extern int optind;    // 记录getopt函数处理argv[]数组的位置,一般不需要设置
	extern int opterr;    // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
	/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
	   为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
	extern int optopt; 
	static int parse_option(int argc, char* argv[], int* mode)
	{
	    int opt;
	    if (3 != argc) {
	        print_usage();
	        return -1;       
	    }
	    while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
	        switch (opt) {
	        case 'a':
	            if ('b' == *optarg) mode = 0;
	            else if (0 == strcmp(optarg, "nb")) *mode = 1;
	            else if (0 == strcmp(optarg, "poll")) *mode = 2; 
	            else if (0 == strcmp(optarg, "select")) *mode = 3;
	            else if (0 == strcmp(optarg, "signal")) *mode = 4;
	            else return -1; 
	            break;
	        case ':':
	            print_usage();
	            return -1;
	        case '?':
	            print_usage();
	            return -1;
	
	        default:
	            print_usage();
	            return -1;
	        }
	    }
	    return 0;
	}
	static int fd;
	static unsigned int push_cnt = 0, release_cnt = 0;
	static void read_key(int fd)
	{
	    int len;
	    char val = 0xFF;         
	    len = read(fd, &val, sizeof(val));
	    if (sizeof(val) == len) {
	        if (1 == val) {
	            push_cnt++;
	            printf("push the button %d\n", push_cnt);
	        } 
	        if (0 == val) {
	            release_cnt++;
	            printf("release the button %d\n", release_cnt);
	        }
	    }
	}
	// 信号处理函数
	static void my_signal_fun(int signum)
	{
	    read_key(fd);
	}
	
	int main(int argc, char* argv[])
	{
	    int len, ret;
	    int flags = 0, mode = 0;
	    char val = 0xFF;
	    struct pollfd fds = {0};
	    struct timeval timeout = {0};
	    fd_set read_fds;
	    ret = parse_option(argc, argv, &mode);
	    if (0 != ret) return -1;
	
	    switch (mode) {
	    case 0:
	        fd = open(PATH, O_RDONLY); // 阻塞打开
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) read_key(fd);
	        break;
	    case 1:
	        fd = open(PATH, O_RDONLY | O_NONBLOCK);  // 非阻塞打开
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) read_key(fd);
	        break;
	    case 2:
	        fd = open(PATH, O_RDONLY); 
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        fds.fd = fd;
	        fds.events = (POLLIN | POLLRDNORM);
	        while (1) {
	            //fds.revents = 0;
	            ret = poll(&fds, 1, 4000);  // 超时时间设置为4秒,
	            if (ret < 0) {
	                printf("poll error\n");
	                close(fd);
	                return -1;
	            } 
	            else if ((fds.revents & POLLIN) || (fds.revents & POLLRDNORM))
	                read_key(fd);
	            else printf("poll timeout\n");
	        }
	        break;
	    case 3:
	        FD_ZERO(&read_fds);
	        fd = open(PATH, O_RDONLY);
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) {
	            FD_SET(fd, &read_fds);      
	            timeout.tv_usec = 0;           
	            timeout.tv_sec = 4;  // 超时时间设置为4秒
	            ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);
	            if (ret < 0) {
	                printf("select error\n");
	                close(fd);
	                return -1;
	            } 
	            else if (FD_ISSET(fd, &read_fds)) 
	                read_key(fd);
	            else printf("select timeout\n");
	        }
	        break;
	    case 4:
	        fd = open(PATH, O_RDONLY); 
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        fcntl(fd, F_SETOWN, getpid());
	        flags = fcntl(fd, F_GETFL);
	        fcntl(fd, F_SETFL, flags | FASYNC);
	        signal(SIGIO, my_signal_fun); 
	        while (1) sleep(4);
	        break;
	    default:
	        break;
	    }
	    close(fd);
	    return 0;
	}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
关于java程序员发展需要学习的路线整理集合 技术 应用技术 计算机基础知识 cpu mem disk net 线程,进程 第三方库 poi Jsoup zxing Gson 数据结构 树 栈 链表 队列 图 操作系统 linux 代码控制 自动化代码检查 sonar 代码规范 阿里巴巴Java开发规范手册 UMPAY——编码规范 日志规范 异常规范 网络 协议 TCP/IP HTTP hession file HTTPS 负载均衡 容器 JBOSS tomcat resin jetty 容灾 日志框架 开源框架 slf4j 框架实现 log4j logback commong logging jdk logger 测试框架 测试框架 junit easymock testng mockito bug管理 禅道 jira 开发工具 编程工具 eclipse myeclipse idea vi VS webstorm sublime text 版本控制 svn git 项目管理 maven Nexus Jenkins 工作软件 反编译软件 office系列 下载器 adobe系列 记录软件 思维导图 office--Note 邮件管理 性能优化 分层优化 系统级别 中间件级别 JVM级别 代码级别 分段优化 前端 web应用 服务应用 资源池 数据库 大数据与nosql zookeeper hadoop hbase mongodb strom spark java语言 语言语法基础 异常 泛型 内部类 反射 序列化 nIo 匿名类 包装类 优先级 引用 语言工具类库 容器类 集合 链表 map 工具类 系统类 日期类 数字类 字符串+正则 流 字符流 字节流 语言特性 继承 封装 多态 JVM 多线程与并发 GC机制 GC收集器类型 串行 CMS 并行 G1 算法 复制 标记清理 标记整理 分区 新生代 eden survivor 老年代(old区) 永久代(perm区) 版本变化 1.5 1.6 1.7 1.8 1.9 IO/NIO IO类型 同步阻塞 同步非阻塞 基于信号 多路复用 异步IO 类加载机制 双亲委派 OSGI 算法 搜索 二分 排序 选择 冒泡 插入 快速 归并 堆 桶 基数 常用算法 贪婪 回溯 剪枝 动态规划 数据挖掘算法 KMP算法 GZZ算法 HASH分桶 关联规则算法 APRORIVE算法 分布式 负载均衡 水平伸缩 集群 分片 Key-hash 异步 一致性hash 消峰 分库分表 锁 悲观锁 乐观锁 行级锁 分布式锁 分区排队 一致性 一致性算法 paxos zab nwr raft gossip 柔性事务(TCC) 一致性原理 CAP BASE 中间件 数据库 mysql 存储引擎 索引 锁 oracle db2 缓存 redis 数据结构 持久 复制 cas 单线程 memcache eacache Tair 消息队列 jms Queue Topic kafka 持久 复制 Stream Partition rocketMQ RabbitMQ ActiveMQ 常用开源框架 Spring Spring MVC Spring WebFlow spring tx aop ioc Struts ibatis Mybatis CAS Dubbo 工作能力 软实力 应急能力 创新能力 管理能力 分享能力 学习能力 沟通能力 解决问题能力 经历 技术攻关案例 程序开发案例 程序设计案例 设计 设计原则 单一职责原则 开闭原则 里氏替换原则 依赖倒转原则 接口隔离原则 迪米特原则 设计模式 结构模式 适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 创建模式 抽象工厂模式 工厂方法模式 建造这模式 原型模式 单例模式 行为模式 责任链模式 命令模式 解释器模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 模板方法模式 访问者模式 设计案例 UML 架构 系统架构能力 基本理论 扩展性设计 可用性设计 可靠性设计 一致性设计 负载均衡设计 过载保护设计 协议设计 二进制协议 文本协议 接入层架构设计 DNS轮询 动静态分离 静态化 反向代理 LVS F5 CDN 逻辑层架构设计 连接池 串行化技术 影子Master架构 批量入 配置中心 去中心化 通讯机制 同步 RPC RMI 异步 MQ Cron 数据层架构设计 缓存优化 DAO&ORM; 双主架构 主从同步 读分离 性能优化架构能力 代码级别 关联代码优化 cache对其 分支预测 copy on write 内联优化 系统优化 cache 延迟计算 数据预读 异步 轮询与通知 内存池 模块化 工程架构能力 开发语言 运维与监控 监控 系统监控 日志监控 流量监控 接口监控 数据库监控 业务监控 性能监控 告警 日志 设计模式 数据结构与算法 各种工具
python入门到高级全栈工程师培训视频学习资料;本资料仅用于学习,请查看后24小时之内删除。 【课程内容】 第1章 01 计算机发展史 02 计算机系统 03 小结 04 数据的概念 05 进制转换 06 原码补码反码 07 物理层和数据链路层 08 网络层和arp协议 09 传输层和应用层 第2章 01 上节课复习 02 arp协议复习 03 字符编码 第3章 01 网络基础和dos命令 02 为何学习linux 03 课程内容介绍 04 操作系统内核与系统调用 05 操作系统安装原理 06 linux操作系统安装 07 初识linux命令 08 linux操作系统目录结构 09 目录及文件操作 第4章 01 上节课复习 02 创建用户相关的文件 03 用户增删该查及组相关操作 04 对文件的权限管理 05 对目录的权限管理 06 权限管理补充 07 属主属组及基于数字的权限管理 第5章 01 上节课复习 02 文件合并与文件归档 03 文件归档与两种压缩方式 04 vim编辑器 05 系统启动流程 06 grub加密 07 bios加密 08 top命令 09 free命令 10 进程管理 第6章 01 上节课复习 02 磁盘分区 03 文件系统与挂载 04 挂载信息讲解 05 磁盘用满的两种情况 06 软连接和硬链接 07 软连接和硬链接补充 第7章 01 ip地址与子网划分 02 ip地址配置 03 虚拟机网络模式 04 三层隔离验证试验 第8章 01 上节课复习 02 软件包介绍 03 rpm软件包管理 04 yum软件包管理 05 源码安装python3.5 06 ssh服务 07 apache服务 08 samba服务 第9章 01 Python开发系列课程概要 02 Python作业要求以及博客 03 编程语言介绍 04 Python种类介绍 05 Python安装以及环境变量的操作 06 Python初识以及变量 07 Python条件语句和基本数据类型 08 Python while循环语句以及练习题 09 练习题讲解 第10章 01 上节内容回顾以及补充 02 上周作业实现 03 Pycharm的安装和使用 04 Python 运算符 05 Python 运算符以及总结 06 Python 基本数据类型介绍 07 Python 整形的魔法 08 Python 字符串的魔法 第11章 01 Python 字符串的魔法 02 Python range的用法以及练习 03 Python 课上练习解释 04 Python 基础知识练习题试题 第12章 01 今日内容介绍以及基础测试题答案讲解 02 Python 列表的魔法 03 Python 元组的魔法 04 Python 字典的魔法 05 Python 错误更正:布尔值可以作为字典的key 06 Python 今日内容整理 第13章 第13章共1课 第14章 01 数据类型和变量总结 02 集合定义和基本操作方法 03 集合关系运算交,差,并集 04 集合的其他内置方法 05 集合补充 06 百分号字符串拼接 07 format字符串格式化 08 数学意义的函数与python中的函数 09 为何要有函数 10 函数返回值 11 可变长参数 第15章 01 上节课复习 02 全局变量与局部变量 03 风湿理论之函数即变量 04 函数递归 05 函数递归补充 第16章 01 上节课回顾 02 函数作用域 03 函数作用域补充 04 匿名函数 05 函数式编程介绍 06 函数式编程尾递归调用优化 07 map函数 08 map函数filter函数 09 reduce函数 10 map reduce filter总结 11 内置函数part1 第17章 01 课前吹牛 02 zip方法 03 max和min高级使用 04 其他内置函数 05 文件操作的其他模式 第18章 01 上节课复习 02 文件处理b模式 03 文件操作的其他方法 04 文件seek方法补充 05 迭代器协议和for循环工作机制 06 迭代器补充 07 三元运算,列表解析,生成器表达式 第19章 01 生成器函数 02 生成器函数的好处 03 母鸡下蛋的传说 04 生成器特性阐释 05 生产者消费者模型 06 第三次作业讲解 第20章 01 上节课回顾 02 装饰器基本理论 03 高阶函数使用 04 函数闭包 05 函数闭包装饰器基本实现 06 函数闭包加上返回值 07 函数闭包加上参数 08 函数闭包补充:解压序列 09 函数闭包为函数加上认证功能 10 函数闭包模拟session 11 函数闭包装饰器运行流程 12 函数闭包带参数装饰器 第21章 01 查询功能 02 修改功能 03 程序的解耦 04 module模块和包的介绍 05 模块的执行以及__name__ 06 关于模块的介绍 07 time时间模块 08 random模块 第22章 01 模块的补充 02 sys修改环境变量 03 BASEDIR的介绍 04 os模块的介绍 05 sys模块的介绍 06 json模块 07 pickle模块 08 shelve模块 09 XML模块 10 re模块简介 11 re模块之元字符 第23章 01 re模块之转义字符 02 re模块之分组 03 re模块之方法 04 re模块总结 05 logging模块 06 re模块补充 07 configparse模块 08 hashlib模块 09 计算器作业以及思路 10 模块导入补充 第24章 01 面向对象设计 02 类相关知识 03 对象相关知识 04 类属性增删改查 05 实例属性的增删改查 06 对象与实例属性 07 对象与实例属性补充 08 面向对象作业 第25章 01 上节课回顾 02 静态属性 03 类方法 04 静态方法 05 小结 06 组合 07 继承 08 接口继承与归一化设计 09 继承顺序之mro线性顺序列表 10 在python2中的继承顺序是什么 11 在子类中调用父类方法 12 super调用父类的方法 13 选择系统作业讲解 第26章 01 学生自主复习 02 分享列表 03 多态 04 封装 05 面向对象概念总结 06 反射 07 反射及动态导入模块 08 类的内置attr属性 09 类内置attr属性补充 10 继承的方式完成包装 11 组合的方式完成授权 第27章 01 os模块复习 02 上节课复习 03 内置函数补充及getattribute 04 getattribue补充 05 item系列 06 str与repr 07 自定制format 08 slots属性 09 doc属性 10 module和class 11 析构方法 12 call方法 13 迭代器协议 14 迭代器协议实现斐波那契数列 16 描述符答疑 17 描述符优先级 18 软件开发规范 19 pycharm干的好事 第28章 01 上节课复习 02 上下文管理协议 04 异常的构成简单了解 05 描述符应用 08 类的装饰器的基本原理 09 类的装饰器增强版 10 类的装饰器的应用 11 自定制property 12 自定制property流程分析 13 自定制property实现延迟计算功能 14 property补充 15 元类介绍 16 自定义元类 17 函数复习 18 文件操作复习 第29章 01 上节课复习 02 什么是异常处理及异常处理的两种方式对比 03 多分支与万能异常 04 异常处理的其他内容 05 什么时候用异常处理 06 什么是socket 07 套接字发展及分类 08 基于tcp协议的套接字编程 09 socket底层工作原理解释 10 tcp三次握手与四次挥手 第30章 01 上节课复习 02 客户端服务端循环收发消息 03 socket收发消息原理剖析 04 服务端循环链接请求来收发消息 05 补充 06 udp套接字 07 recv与recvfrom的区别及基于udp实现ntp服务 08 基于tcp实现远程执行命令 09 基于tcp实现远程执行命令测试结果 10 粘包现象 11 粘包解决方法 第31章 01 上节课复习 02 socketserver实现并发 03 socketserver模块介绍 04 socketserver源码分析tcp版本 05 socketserver源码分析udp版 06 ftp作业要求讲解 07 补充:认证客户端链接合法性 第32章 01 FTP之参数解析与命令分发 02 FTP之逻辑梳理 03 FTP之验证功能 05 FTP之文件上传 06 FTP之断点续传 08 FTP之进度条 09 FTP之cd切换 11 FTP之创建文件夹及MD5校验思路 第33章 01 操作系统历史 02 进程的概念 03 线程的概念 04 线程的调用以及join方法 05 setDaemon方法和继承式调用.baiduyun.downloading 05 setDaemon方法和继承式调用 第34章 01 上节知识回顾 02 并发并行与同步异步的概念 03 GIL的概念 04 同步锁 05 递归锁 06 同步对象event 07 信号量 08 线程队列 09 生产者消费者模型 10 多进程的调用 第35章 01 进程通信 02 进程池 03 协程 04 事件驱动模型 05 IO模型前戏 06 阻塞IO非阻塞IO 07 select及触发方式 08 select监听多连接 09 select与epoll的实现区别 第36章 01 异步IO 02 selectors模块介绍 03 selectors模块应用 04 作业介绍 第37章 01 selctors实现文件上传与下载 02 html的介绍 03 html文档树的概念 04 meta标签以及一些基本标签 05 img标签和列表标签 06 form表单之input标签 07 通过form向server端发送数据 08 form表单之select标签 09 table标签 第38章 01 css的四种引入方式 02 css的四种基本选择器 03 css的组合选择器 04 css的属性选择器 05 css的伪类 06 css的选择器优先级 07 css的背景属性 第39章 01 css的文本属性与边框属性 02 css的列表属性与display属性 03 css的内外边距 04 css的内外边距补充 05 css的float属性 06 css的清除浮动 07 css的定位 08 css的margin定位 第40章 01 抽屉作业之head区域(导航条) 02 抽屉作业之置顶区域 03 抽屉作业之content部分 05 抽屉作业之页码部分 06 抽屉作业之footer部分 第41章 01 JS的历史以及引入方式 02 JS的基础规范 03 JS的基本数据类型 04 JS的运算符 05 JS的控制语句与循环 06 JS的循环与异常 07 JS的字符串对象 08 JS的数组对象 09 JS的函数对象 第42章 01 JS的函数作用域 02 JS的window对象之定时器 03 JS的history对象和location对象 04 JS的DOM节点 05 JS的DOM节点 第43章 01 上节知识回顾 02 js之onsubmit事件与组织事件外延 03 DOM节点的增删改查与属性设值 04 正反选练习 05 js练习之二级联动 06 jquery以及jquery对象介绍 07 jquery选择器 08 jquery的查找筛选器 09 jquery练习之左侧菜单 第44章 01 jquery属性操作之html,text,val方法 02 jquery循环方法和attr,prop方法 03 jquery模态对话框与clone的应用 04 jqueryCSS操作之offsets,position以及scrolltop 05 jquery事件绑定与事件委托 06 jquery动画效果 07 jquery扩展与插件 08 jquery扩展补充 09 本周作业轮播图以及思路 第45章 轮播图片css部分 轮播图片js部分 第46章 01 数据库与dbms的概念 02 sql规范 03 数据库操作DDL 04 python s3 day46 mysql的数据类型 05 数据表操作 06 表记录之增删改操作 07 表记录查询之查询 第47章 01 多表查询之连接查询 02 级联删除与set null 03 多表查询之连接查询 04 多表查询之复合查询与子查询 05 mysql之索引 第48章 01 python操作数据库pymysql 02 数据库之事务 03 mysql事务之savepoint 第49章 01 http协议之请求协议 02 http协议之响应协议 03 web框架的概念 04 做一个最简答web框架 05 MVC模式和MTV模式 06 django的一个简单应用 07 django静态文件之static 08 django的url控制系统 09 django的urlConf补充 第50章 01 django之视图函数的介绍 02 django视图之redirec 03 django模板之变量 04 django模板之过滤器 05 django模板之控制语句if和for循环 06 django模板之标签tag补充 07 django模板之自定义filter和simple_tag 08 django模板之继承标签extend和添加标签include 第51章 01 数据库表与表之间的一对多多对多的关系 02 Django的ORM的概念 03 ORM对单表的增删改操作 04 ORM查询API 05 模糊查询之万能的双下换线 第52章 01 上节知识回顾 02 ORM多表操作之一对多增加记录 03 ORM多表操作之一对多查询之对象查询 04 ORM多表操作之一对多查询之双下划线查询 05 ORM多表操作之多对多添加记录 06 ORM多表操作之多对多查询 07 ORM多表操作之F查询与Q查询 08 ORM的querySet集合对象的特性 第53章 01 admin介绍 02 alex首秀失败 03 自定义admin样式 04 admin补充 05 COOKIE介绍 06 COOKIE和SESSION配合使用 第54章 01 今日内容概要 02 Django内容回顾 03 Django请求生命周期之Http请求 04 Django请求生命周期之FBV和CBV 05 Django请求生命周期之CBV扩展 06 瞎扯淡 07 Django请求生命周期之响应内容 08 学员管理示例:数据库设计 09 学员管理示例:班级管理 10 学员管理示例:学员管理 第55章 01 Django的ORM基本操作补充之概要 02 Django的ORM基本操作补充之一对多 03 学员管理示例:编辑学生 04 Django的ORM基本操作补充之多对多 05 学员管理示例:为班级分配老师 06 初识Ajax以及简单应用 07 学员管理示例:Ajax删除学生 08 本节作业以及内容补充 第56章 01 上节内容回顾 02 创建Project以及表结构 03 基于BootStrap和FontAwesome制作页面 04 创建学生信息 05 删除学生信息 第57章 01 上节内容回顾 02 上节bug修复 03 编辑学生信息之前端功能 04 编辑学生信息之后台处理 05 以上内容总结 06 Ajax功能之dataType和traditional 第58章 01 今日内容概要 02 Ajax补充之serialize 03 分页功能介绍 04 分页基础知识 05 Django内置分页 06 扩展Django内置分页 07 自定义分页组件 08 DjangoForm组件初识 第59章 01 Form组件之生成HTML标签 02 Form组件之详解字段 03 Form组件之常用标签示例 04 Form组件之动态绑定数据 第60章 Django序列化共6课 第61章 01 上节内容回顾 02 上传文件 03 制作上传按钮 04 Form组件上传文件 05 上传相关内容梳理 06 Model操作知识提问 07 Model操作概述 08 Model字段 09 Model连表字段参数详解 10 Model自定义多对多第三张表 11 强插一道面试题 12 Model连表操作梳理 13 多对多自关联 14 Model操作补充 15 再插两道JavaScript面试题 16 Model操作之select_related以及prefetch_related 17 Model操作知识梳理以及补充 18 JavaScript两道面试题讲解 第62章 01 今日内容概要 02 创建基本项目 03 XMLHttpRequest对象发送请求 04 XMLHttpRequest对象发送POST请求 05 Iframe伪造Ajax请求 06 Iframe伪造回调函数 07 上述内容整理 08 FormData对象以及Ajax文件上传 09 Iframe文件上传 10 Iframe上传文件 11 JSONP实现AJax跨域 12 内容整理以及CORS简单介绍 第63章 01 项目以及学习介绍 02 企业官网示例功能介绍 03 企业官网示例以及数据库表结构 04 企业官网示例作业要求 05 Toando源码基本基本介绍 第64章 01 组合搜索 02 瀑布流 03 瀑布流作业 第65章 01 今日内容概要 02 瀑布流作业讲解 03 保障系统需求分析 04 保障系统数据库设计 05 保障系统目录结构规定 06 阶段任务安排 第66章 01 保障系统主页功能讲解 02 保障系统主页分类和分页的实现 03 阶段作业:保障系统登录注册 第67章 01 保障系统之登录注册功能讲解 02 保障系统之网站验证码 03 保障系统之一个月免登陆 04 任务安排 第68章 01 保障系统之个人知识库主页 02 保障系统之个人知识库内容筛选 03 保障系统之文章最终页 04 保障系统之KindEditor基本使用 05 保障系统之下节预告 06 今日作业以及下节预告 第69章 01 后台管理功能介绍 02 后台管理页面布局 03 今日作业以及下节预告 第70章 01 后台管理之创建报障单 02 后台管理之处理报障单 03 后台管理之画图流程 04 后台管理之Highchart统计保障单 第71章 01 权限管理要求 02 权限管理数据库表设计 03 填充权限数据 04 作业:获取权限以及菜单信息 05 作业思路讲解 06 权限管理之获取用户权限信息 07 权限管理之获取用户菜单信息 08 权限管理之用户权限挂靠到菜单上 09 权限管理之处理菜单等级关系 第72章 01 上节内容概要以及标记应该显示的菜单 02 权限管理之递归生成多级菜单 03 权限管理之标记当前以及激活菜单 04 权限管理之基本使用 05 权限管理之封装权限组件 06 下节预告 第73章 01 CMDB项目介绍 02 CMDB开发背景 03 CMDB开发目的 04 CMDB资产采集方式之agent 05 CMDB资产采集方式之ssh 06 CMDB资产采集方式之saltstack 07 CMDB资产采集方式之puppet 08 CMDB资产采集方式比较 09 CMDB资产采集内容梳理 10 CMDB资产采集功能实现之agent 11 CMDB资产采集功能实现之ssh 12 CMDB资产采集功能实现之saltstack 13 CMDB资产采集插件开发 14 内容回顾之面向对象继承 15 作业:基于配置文件加载插件 第74章 01 CMDB项目上节作业讲解 02 CMDB项目采集资产数据 03 知识拾遗之线程进程池 04 CMDB项目采集资产之日志记录 05 自定义JSON序列化类型 06 本节作业 第75章 01 基于requests模块汇报资产数据 02 基于requests模块汇报API验证 03 CMDB项目示例之API验证流程 04 CMDB项目之数据库表结构 05 CMDB项目之资产汇报并持久化 06 CMDB项目之持久化资产流程 07 本周作业 第76章 01 CMDB项目CURD组件之配置文件构造 02 CMDB项目CURD组件之神奇的单@符号 03 CMDB项目CURD组件之神奇的双@符号 04 CMDB项目CURD组件之自定义td属性 05 下节内容预习 第77章 01 CMDB项目CURD组件之进入编辑模式 02 CMDB项目CURD组件之全选取消反选和编辑模式 03 CMDB项目CURD组件之内容截图 04 CMDB项目CURD组件之进入编辑模式详细 05 CMDB项目CURD组件之属性中应用神奇的单@符号 06 CMDB项目CURD组件之退出编辑模式 07 CMDB项目CURD组件之更新数据 08 CMDB项目CURD组件之基于jQuery扩展封装组件 09 CMDB项目CURD组件之10分钟搞定页面基本操作 10 CMDB项目CURD组件之分页功能 11 CMDB项目CURD组件之搜索功能介绍 12 CMDB项目总结 第78章 01 剩余项目概览 02 CRM项目需求分析 04 CRM项目需求分析及架构设计 05 CRM项目表结构设计 第79章 01 CRM项目实战-前端页面布局 02 CRM项目实战-登录页面开发 04 CRM项目实战-动态菜单设计 05 CRM项目实战-kingadmin开发设计 06 CRM项目实战-kingadmin自动发现及注册功能开发 07 CRM项目实战-kingadmin model obj list页面开发 08 CRM项目实战-kingadmin 根据list_display配置生成数据列表 09 CRM项目实战-kingadmin 多条件过滤功能开发 第80章 01 课前鸡汤 02 分页功能开发 03 分页功能优化 04 排序功能开发 05 分页 排序 筛选组合使用 06 搜索功能开发 第81章 01 CRM项目实战 - 动态modelform的实现 02 CRM项目实战 - 动态modelform 增加自定义样式 03 CRM项目实战 - 实现任意表的增删改查 04 CRM项目实战 - 只读字段的处理 05 CRM项目实战 - filter_horizontal的实现 第82章 01 CRM项目实战 - kingadmin m2m filter_horizontal优化 02 CRM项目实战 - kingadmin 对象删除功能开发 03 CRM项目实战 - kingadmin+admin+action功能开发 04 CRM项目实战 - csrf+token验证原理 第83章 01 CRM项目-kingadmin批量删除 02 CRM项目-学员报名流程开发 03 CRM项目-本次作业需求 第84章 01 SSO介绍 02 用户自定义认证 03 万能通用权限框架设计 04 万能通用权限框架设计-自定义权限钩子实现 第85章 01 堡垒机项目实战-需求讨论 02 堡垒机项目实战-表结构设计 第86章 01 堡垒机项目实战-用户交互程序开发 02 堡垒机项目实战-通过paramiko记录ssh会话记录 03 堡垒机项目实战-把parmaiko代码嵌入用户交互程序 04 堡垒机项目实战-在数据库里记录用户会话数据 第87章 01 堡垒机项目实战-前端模板的选择 02 堡垒机项目实战-web ssh的使用 03 堡垒机项目实战-批量任务的思路 04 堡垒机项目实战-批量任务的前端页面开发 第88章 01 堡垒机实战-批量命令后端开发 02 堡垒机实战-批量命令前端获取执行结果 03 堡垒机实战-批量文件分发 第89章 01 版本管理工具介绍 02 git基本使用 03 github使用 04 git 分支开发流程 05 restful规范介绍 06 restful api设计指南 第90章 01 rabbitmq 消息安全接收 02 rabbitmq 消息持久化 03 rabbitmq 消息订阅发布 04 rabbitmq 消息组播 05 rabbitmq 消息RPC 第91章 01 阶段课程安排介绍 02 爬虫介绍 03 初识爬虫之采集汽车资讯信息 04 requests和beautfulsoup模块基本使用 05 示例:自动登录抽屉新热榜 06 requests模块详细介绍 第92章 01 BeautifulSoup模块详细介绍 02 示例:自动登录知乎 03 示例:自动登录博客园 04 作业之开发Web微信 第93章 01 开发Web微信前戏 02 Web微信流程介绍 03 Web微信之用户扫码 04 Web微信之用户信息初始化 05 Web微信开发总结 第94章 01 Web微信之获取用户初始化信息并展示 02 Web微信之发送微信消息 03 Web微信之获取用户消息 04 Web微信开发总结 第95章 01 多线程实现并发请求 02 多进程实现并发请求 03 异步IO模块的使用 04 自定义异步IO模块前戏 05 自定义异步IO模块开发 06 自定义异步IO模块开发 第96章 01 Scrapy爬虫介绍 02 抽屉示例:初识Scrapy并获取新闻标题 03 抽屉示例:递归获取所有页码 04 抽屉示例:基于pipeline实现标题和URL持久化 第97章 01 Scrapy上节回顾 02 去除重复URL 03 pipeline补充 04 自动登录抽屉并点赞 05 scrapy框架扩展 06 配置文件 第98章 01 Scrapy配置之自动限速以及缓存 02 Scrapy之默认代理以及扩展代理 03 Scrapy之自定义Https证书 04 Scrapy配置之下载中间件 05 Scrapy配置之爬虫中间件 06 Scrapy配置之自定义scrapy命令 07 Scrapy源码流程简述 第99章 01 今日内容概要 02 Scrapy源码剖析前戏之Twisted使用 03 Scrapy源码剖析之自定义Low版框架 04 Scrapy源码剖析之自定义TinyScrapy框架 05 Scrapy源码剖析流程解析 第100章 01 Tornado学习概要 02 Tornado基本操作 03 Tornado自定义Session知识前戏 04 Tornado自定义Session 第101章 01 WebSocket介绍 02 WebSocket握手过程分析 03 基于Python实现WebSocket握手过程 04 位运算补充 05 WebSocket数据解析过程 06 基于Tornado的WebSocket实现聊天室 第102章 01 今日内容复习目标 02 异步非阻塞框架介绍 03 Tornado异步非阻塞功能使用 04 自定义Web框架(同步) 05 自定义Web框架支持同步和异步非阻塞 06 复习计划 第103章 01 缓存数据库介绍 02 redis string操作 03 redis hash 操作 04 redis list操作 05 redis 集合操作 06 redis 其他常用命令 07 redis 发布订阅 08 celery介绍和基本使用 09 celery在项目中使用 10 celery定时任务 11 celery在项目中使用 12 celery 在django中实现定时任务 第104章 就业指导 01 简历制作 02 如何面试 03 最后的鸡汤 04 Nginx+uWSGI+Django部署
要让字符设备驱动支持阻塞非阻塞IO模型,需要在驱动程序中使用select/poll机制,同时使用文件操作的O_NONBLOCK标志。下面以read操作为例,简要说明如下: 1. 在设备驱动程序中,使用`file_operations`结构体中的`read`函数实现读操作,同时在该函数中添加对阻塞非阻塞IO模型的支持。 2. 对于阻塞IO模型,可以直接在`read`函数中调用`wait_event_interruptible`函数使进程进入睡眠状态,等待数据就绪后再唤醒进程。当然在等待数据就绪的过程中,如果进程接收到了信号,则需要立即返回`-ERESTARTSYS`。 3. 对于非阻塞IO模型,需要在`read`函数中使用`O_NONBLOCK`标志进行判断。如果该标志被设置,则可以直接调用`poll_wait`函数等待数据就绪,如果数据没有准备好,则直接返回`-EAGAIN`。 4. 在`poll`函数中,需要添加对于设备文件的监控,以便在数据就绪时通知进程。这可以通过在驱动程序中添加`poll`函数来实现。在该函数中,需要使用`poll_wait`函数将当前进程添加到等待队列中,并在数据就绪时唤醒进程。 下面是一个简单的代码示例: ```c static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue); static unsigned int mydevice_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; struct mydevice_data *data = file->private_data; poll_wait(file, &read_wait_queue, wait); if (data->data_ready) { mask |= POLLIN | POLLRDNORM; } return mask; } static ssize_t mydevice_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct mydevice_data *data = file->private_data; ssize_t ret = 0; if (file->f_flags & O_NONBLOCK) { /* 非阻塞IO模型 */ if (!data->data_ready) { return -EAGAIN; } } else { /* 阻塞IO模型 */ wait_event_interruptible(read_wait_queue, data->data_ready); if (signal_pending(current)) { return -ERESTARTSYS; } } /* 读取数据 */ if (copy_to_user(buf, data->buffer, data->size)) { ret = -EFAULT; } else { ret = data->size; data->data_ready = 0; } return ret; } ``` 在上面的代码示例中,`mydevice_read`函数是`read`操作对应的函数,`mydevice_poll`函数是`poll`操作对应的函数。其中,`wait_event_interruptible`函数用于阻塞进程,等待数据就绪;`poll_wait`函数用于将进程添加到等待队列中,等待数据就绪时唤醒进程。同时,根据文件操作的O_NONBLOCK标志,判断当前使用的是阻塞IO模型还是非阻塞IO模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值