赞
踩
目录
2.2 const struct file_operations *fops
头文件
驱动模块装载入口和卸载入口声明
模块装载函数和卸载函数
GPL声明
//头文件 #include<linux/init.h> #include<linux/module.h> //模块加载入口函数实现 static int __init (函数名1)(void) { //资源的创建,申请,创建驱动 return 0; } //模块卸载入口函数实现 static void __exit (函数名2)(void) { //资源的释放,删除驱动 } //模块入口声明 //装载声明(内核加载的入口指定) module_init(函数名1);//只要加载就执行其中声明的函数 //卸载声明(内核卸载的入口指定) module_exit(函数名2); //GPL开源声明 MODULE_LICENSE("GPL");
insmod
功能:加载模块
命令:insmod 模块路径/模块名 例如:insmod /lib/modules/yhw.ko
rmmod
功能:卸载模块
命令:rmmod 模块路径/模块名
lsmod
功能:查看已经加载的模块
如果模块中内容需要在其他模块中使用,可以把内容进行导出--->添加导出声明
EXPORT_SYMBOL(内容名字);
在要使用的模块中进行声明
如果模块只用于进行导出,可以不写入口声明以及定义
举例:
- //math.c
- #include <linux/init.h>
- #include <linux/module.h>
-
- int myadd(int a,int b)
- {
-
- return a+b;
-
- }
-
- int mysub(int a,int b)
- {
- return a-b;
- }
-
- EXPORT_SYMBOL(myadd);
- EXPORT_SYMBOL(mysub);
-
-
- static int __init math_init(void)
- {
- return 0;
- }
-
- static void __exit math_exit(void)
- {
-
- }
-
- module_init(math_init);
- module_exit(math_exit);
-
- MODULE_LICENSE("GPL");
- //hello.c
- #include <linux/init.h>
- #include <linux/module.h>
-
- int myadd(int a,int b);
- int mysub(int a,int b);
-
- static int __init hello_init(void)
- {
- //资源的创建,申请,创建驱动
-
- printk("%d\n",myadd(1,2));
- return 0;
- }
-
- static void __exit hello_exit(void)
- {
- //资源的释放,删除驱动
- printk("%d\n",mysub(1,2));
-
- }
-
-
- module_init(hello_init);
- module_exit(hello_exit);
- MODULE_LICENSE("GPL");
上面写了两个模块,一个是math.c 一个是hello.c 那么我们想要在hello.c模块中使用math.c模块中的内容,就需要在math.c中添加声明:
- EXPORT_SYMBOL(myadd);
- EXPORT_SYMBOL(mysub);
表示我们要在其它模块使用本模块中的两个函数myadd和mysub,然后在hello.c中定义并传参。这样就实现在其它模块中使用另一个模块中的内容。(导出的内容为函数或全局变量)
但是!在编写驱动时,有些变量是不确定的,是根据驱动具体加载到哪个设备才确定,在进行
insmod装载驱动模块时再传递这些参数值,那么如何处理参数传递呢?
module_param(name,type,perm)
参数1:参数的名字,变量名
参数2:参数的类型,int,char
参数3:/sys/modules 文件的权限,0666例如:
module_param(a,int,0644); module_param(b,int,0644);
在内核中,有很多的设备驱动,所以需要一个设备号进行区分;
用户程序也必须知道设备驱动对应到哪个设备节点(文件);
在Linux系统下/proc/devices:文件中包含了所有注册到内核的设备,可以打开看一下
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
参数:
参数1:
unsigned int major:主设备号
设备号:32bit == 主设备号(12bit) + 次设备号(20bit)
主设备号:表示同一类型的设备
次设备号:表示同一类型中的不同设备
参数有两种设置:
静态:例如指定一个整数:250----主设备号为250
动态:让内核随机指定,参数为0
参数2:
const char *name:一个字符串,描述设备信息,自定义
参数3:
const struct file_operations *fops:结构体指针,结构体变量的地址(结构体中就是应
用程序和驱动程序函数关联,open、read、write)---文件操作对象,提供open、read、write等驱动中的函数
返回值:
如果是静态指定主设备号,返回0表示申请成功,返回负数表示申请失败
如果是动态申请,返回值就是申请成功的主设备号
在驱动中要去实现文件IO的接口,通过结构体指针,指向一个结构体,这个结构体当中提供了驱动与应用程序文件IO的接口关联。
- struct file_operations {
- struct module *owner;
- loff_t (*llseek) (struct file *, loff_t, int);
- ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
- ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long,
- loff_t);
- ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long,
- loff_t);
- int (*iterate) (struct file *, struct dir_context *);
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
- long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
- long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
- int (*mmap) (struct file *, struct vm_area_struct *);
- int (*open) (struct inode *, struct file *);
- int (*flush) (struct file *, fl_owner_t id);
- int (*release) (struct inode *, struct file *);
- int (*fsync) (struct file *, loff_t, loff_t, int datasync);
- int (*aio_fsync) (struct kiocb *, int datasync);
- int (*fasync) (int, struct file *, int);
- int (*lock) (struct file *, int, struct file_lock *);
- ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
- unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
- unsigned long, unsigned long);
- int (*check_flags)(int);
- int (*flock) (struct file *, int, struct file_lock *);
- ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
- size_t, unsigned int);
- ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,
- size_t, unsigned int);
- int (*setlease)(struct file *, long, struct file_lock **);
- long (*fallocate)(struct file *file, int mode, loff_t offset,
- loff_t len);
- int (*show_fdinfo)(struct seq_file *m, struct file *f);
- };
- //函数指针集合,其实就是接口,表示文件IO函数用函数指针来表示,存储驱动中的关联函数
- // 如: read函数指针 = xxxx_ok;表示 read函数,在驱动中的关联函数为xxxx_ok函数
//这个功能一般用于驱动中的 read
long copy_to_user(void __user *to,const void *from, unsigned long n)功能:从驱动空间拷贝数据给用户空间
参数:
参数1:
void __user *to:目标地址,应用空间地址
参数2:
const void *from:源地址,内核空间地址
参数3:
unsigned long n:个数
返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完
long copy_from_user(void * to,const void __user * from,unsigned long n)
功能:从用户空间拷贝数据到驱动空间
参数:
参数1:
void *to:目标地址,内核空间地址
参数2:
const void *from:源地址,应用空间地址
参数3:
unsigned long n:个数
返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完
void unregister_chrdev(unsigned int major,const char * name)
参数:
参数1:
unsigned int major:主设备号
参数2:
const char * name:设备信息,自定义
mknod 设备节点名 设备类型 主设备号 次设备号
#如:
mknod /dev/xxx c 250 0
//创建一个类信息
struct class * class_create(owner,name)
参数:
参数1:
owner:一般填写 THIS_MODULE
参数2:
name:字符串首地址,名字,自定义
返回值:
返回值就返回信息结构体的地址
//创建设备节点(设备文件)
struct device * device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
参数:
参数1:
struct class *class:class信息对象地址,通过 class_create()函数创建
参数2:
struct device *parent:表示父亲设备,一般填 NULL
参数3:
dev_t devt:设备号(主设备+次设备)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
参数4:
void *drvdata:私有数据,一般填NULL
参数5、参数6:
const char *fmt, ...:可变参数,表示字符设备文件名(设备节点名)
void device_destroy(struct class * class,dev_t devt)
如果我们想要去控制外设,其实就是控制地址,内核驱动中通过虚拟地址操作,外设是实际的物理地址,每个设备出厂时就有地址
为什么要进行地址映射
1)MMU限制:大多数CPU的MMU只能映射大小为4KB的页面大小,而物理内存中的一块区域大小可能大于4KB,此时需要ioremap来映射不连续的页面。
2)ISA对齐要求:某些外设的寄存器访问需要特定的对齐方式,而CPU访问数据总线的宽度可能无法满足,需要借助ioremap来映射满足要求的虚拟地址。
3)虚拟地址空间不足:当直接映射(网络设备描述符等)占用太多的线性地址空间时,可以使用ioremap来释放部分线性地址,从而获得更多的虚拟地址空间。
4)外设物理地址变化:有些外设允许更改其物理基地址,当基地址变化后,原来的直接映射会失效,此时需要调用ioremap再次建立映射关系。
ioremap实现原理:
利用页表 entries 将不连续的物理页面映射到连续的虚拟地址空间中。它会在页表中设置虚拟地址与物理地址的对应关系,当CPU进行地址翻译时会根据这些关系将虚拟地址转换为物理地址。
API函数
//虚拟地址映射
void *ioremap(phys_addr_t offset, unsigned long size)
参数:
参数1:
phys_addr_t offset:物理地址
参数2:
unsigned long size:映射大小
返回值:
映射之后的虚拟地址
//结束映射
void iounmap(void *addr)
完成地址映射后,返回的值为映射后的地址,就可以对地址进行操作,那么如何操作呢?
第一种 通过指针取*的方式,例如volatile unsigned int *gpx1con;*gpx1con进行操作;
第二种,通过radl() writel()
- //从地址中读取地址空间的值
- u32 readl(const volatile void *addr)
- //将value值,存储到对应地址中
- void writel(u32 value, volatile void *addr)
在模块加载入口中实现:
1) 申请设备号(内核中用于区分和管理不同的字符设备驱动)
2) 创建设备节点(为用户提供一个可操作的文件接口---用户操作文件)
3)实现硬件初始化
地址的映射
实现硬件的寄存器的初始化
中断的申请
4)实现文件io接口(struct file_operations fops结构体)
在卸载入口实现资源释放
iounmap();//解除硬件资源映射
device_destroy();//释放设备节点
class_destroy();//释放节点信息类
unregister_chrdev();//释放设备号
同时,还有出错处理,在某个位置出错,要将之前申请的资源释放;
通过面向对象编程思想,用一个结构体来表示一个对象,设计一个类型来描述一个设备的信息,例如:
- struct BEEP
- {
- unsigned int major;
- struct class * cls;
- struct device * dev;
- unsigned int * pwmtcfg0;
- };
- struct BEEP beep;//表示一个设备对象
- //beep_drv.c
-
-
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/device.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
-
- //硬件地址宏定义
- #define GPD0CON 0x114000A0
-
- #define PWM 0x139D0000
-
- #define PWMTCFG0 0x139D0000
- #define PWMTCFG1 0x139D0004
- #define PWMTCON 0x139D0008
- #define PWMTCNTB0 0x139D000C
- #define PWMTCMPB0 0x139D0010
- //结构体信息存储设备信息,包括class_create创建类信息、device_create创建设备节点、设备号
- struct BEEP
- {
- unsigned int major;
- struct class * cls;
- struct device * dev;
-
- unsigned int * pwmtcfg0;
- unsigned int * pwmtcfg1;
- unsigned int * pwmtcon;
- unsigned int * pwmtcntb0;
- unsigned int * pwmtcmpb0;
- unsigned int * gpd0con;
-
- };
-
- //结构体对象
- struct BEEP beep;
- 应用程序open调用对应接口函数
- int beep_open (struct inode * inode, struct file * filep)
- {
- *(beep.pwmtcon) |= 1;
- return 0;
- }
- //应用程序release调用对应接口,这些通过结构体
- int beep_release (struct inode * inode, struct file * filep)
- {
- *(beep.pwmtcon) &= ~1;
- return 0;
- }
- //file_operations fops结构体中实现接口
- ssize_t beep_write (struct file * filep, const char __user * buf, size_t size, loff_t * fops)
- {
- unsigned int hz = 0;
-
- copy_from_user(&hz,buf,size);//从用户空间拷贝数据
-
- *(beep.pwmtcntb0) = 100000000 / hz;
- *(beep.pwmtcmpb0) = 50000000 / hz;
- *(beep.pwmtcon) |= 1<<1;
- *(beep.pwmtcon) &= ~(1<<1);
- *(beep.pwmtcon) |= 1<<2;
- *(beep.pwmtcon) |= 1;
-
- return 0;
- }
-
-
- //文件IO实现操作
- const struct file_operations fops = {
- .open = beep_open,
- .release = beep_release,
- .write = beep_write,
- };
-
- static int __init beep_init(void)//加载入口
- {
- //资源申请
-
- int ret;
- //申请设备号
- beep.major = register_chrdev(0,"beep",&fops);
- if( beep.major < 0 )//设备号申请失败
- {
- printk(KERN_ERR,"register_chrdev error\n");
- ret = -ENODEV;
- goto err_0;
- }
-
- //创建设备节点
- beep.cls = class_create(THIS_MODULE,"beep cls");
- if(IS_ERR(beep.cls))
- {
- printk(KERN_ERR,"class_create error\n");
- ret = PTR_ERR(beep.cls);
- goto err_1;
- }
-
- beep.dev = device_create(beep.cls,NULL,MKDEV(beep.major,0),NULL,"beep");
- if(IS_ERR(beep.dev))
- {
- printk(KERN_ERR,"device_create error\n");
- PTR_ERR(beep.dev);
- goto err_2;
- }
-
-
- //初始化硬件资源
- //映射寄存器地址
- beep.gpd0con = ioremap(GPD0CON,4);
- if(beep.gpd0con == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_3;
- }
-
- beep.pwmtcfg0 = ioremap(PWMTCFG0,4);
- if(beep.pwmtcfg0 == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_4;
- }
-
- beep.pwmtcfg1 = ioremap(PWMTCFG1,4);
- if(beep.pwmtcfg1 == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_5;
- }
- beep.pwmtcon = ioremap(PWMTCON,4);
- if(beep.pwmtcon == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_6;
- }
- beep.pwmtcntb0 = ioremap(PWMTCNTB0,4);
- if(beep.pwmtcntb0 == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_7;
- }
- beep.pwmtcmpb0 = ioremap(PWMTCMPB0,4);
- if(beep.pwmtcmpb0 == NULL)
- {
- printk(KERN_ERR,"ioremap error\n");
- ret = -ENOMEM;
- goto err_8;
- }
-
- //readl和writel操作地址
- //*gpd0con = *gpd0con & ~(0XF<<0) | (0X2<<0);
- writel(readl(beep.gpd0con) & ~(0XF<<0) | (0X2<<0),beep.gpd0con);
- *(beep.pwmtcntb0) = 200000;
- *(beep.pwmtcmpb0) = 100000;
- *(beep.pwmtcfg0) = *(beep.pwmtcfg0) & ~0xff;
- *(beep.pwmtcfg1) &= ~0XF;
- *(beep.pwmtcon) &= ~(1<<4);
- *(beep.pwmtcon) |= 1<<3;
- *(beep.pwmtcon) |= 1<<1;
- *(beep.pwmtcon) &= ~(1<<1);
- *(beep.pwmtcon) |= 1<<2;
-
- return 0;
- //出错处理
- err_8:
- iounmap(beep.pwmtcntb0);
- err_7:
- iounmap(beep.pwmtcon);
-
- err_6:
- iounmap(beep.pwmtcfg1);
-
- err_5:
- iounmap(beep.pwmtcfg0);
-
- err_4:
- iounmap(beep.gpd0con);
-
- err_3:
- device_destroy(beep.cls,MKDEV(beep.major,0));
-
- err_2:
- class_destroy(beep.cls);
-
- err_1:
- unregister_chrdev(beep.major,"beep");
-
- err_0:
- return ret;
-
- }
-
- static void __exit beep_exit(void)//卸载入口
- {
-
- //资源释放
- //iounmap();
-
- iounmap(beep.pwmtcmpb0);
- iounmap(beep.pwmtcntb0);
- iounmap(beep.pwmtcon);
- iounmap(beep.pwmtcfg1);
- iounmap(beep.pwmtcfg0);
- iounmap(beep.gpd0con);
- device_destroy(beep.cls,MKDEV(beep.major,0));
-
- class_destroy(beep.cls);
-
- unregister_chrdev(beep.major,"beep");
- }
-
-
- module_init(beep_init);
- module_exit(beep_exit);
- MODULE_LICENSE("GPL");
-
-
下面是应用程序:
-
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
-
- int main()
- {
-
- int fd = open("/dev/beep",O_RDWR);
-
- int value = 0;
- while(1)
- {
- scanf("%d",&value);
- if(value == 0)
- break;
- write(fd,&value,4);
- }
- close(fd);
-
- return 0;
- }
-
-
下面是Makefile文件:
- KERNEL_PATH = /home/yhw/Mine/linux-3.14-fs4412 //内核文件路径
-
- CORSS_COMPILE = /opt/arm_none_gcc/gcc-4.6.4/bin/arm-none-linux-gnueabi- //指定交叉编译工具链
- CC = $(CORSS_COMPILE)gcc
-
- obj-m += beep_drv.o //驱动
- APP_NAME = beep_test //应用程序
-
- all:
- make modules -C $(KERNEL_PATH) M=$(shell pwd)
- $(CC) $(APP_NAME).c -o $(APP_NAME)
-
-
- install:
- cp *.ko $(APP_NAME) /home/yhw/Mine/NFS/nfshome/rootfs //拷贝到挂载的nfs目录
-
- clean:
- make clean -C $(KERNEL_PATH) M=$(shell pwd)
- rm $(APP_NAME)
-
通过insmod 加载模块,即可实现蜂鸣器的驱动实现,同理,led也可以这样写。
设备树位置:/arch/arm/boot/dts
设备树介绍:
- //在设备树中添加一个按键设备信息,添加上使用的中断号,启动内核时i,内核中就会有按键信息(中断号)
- key_int_node {
- compatible = "key3";
- interrupt-parent = <&gpx1>;
- interrupts = <2 4>;
- };
在设备中完成信息添加后,在驱动编写中第一步要做的就是获取到中断号
//根据路径获取设备树中的节点
struct device_node *of_find_node_by_path(const char *path);
参数:
参数1:
const char *path:设备树中节点路径
返回值:获取到的节点
//根据节点获取到节点的中断号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
参数:
参数1:
struct device_node *dev:设备节点
参数2:
int index:获取节点中第几个中断号
返回值:获取到的中断号
函数原型:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
功能:对应中断号出现对应的触发方式,就调用申请中指定的函数进行处理
参数:
参数1:
unsigned int irq:设备对应的中断号
参数2:irq_handler_t handler:中断的处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);参数3:
unsigned long flags:中断的触发方式
#define IRQF_TRIGGER_NONE
#define IRQF_TRIGGER_RISING
#define IRQF_TRIGGER_FALLING
#define IRQF_TRIGGER_HIGH
#define IRQF_TRIGGER_LOW0x00000000
0x00000001
0x00000002
0x00000004
0x00000008参数4:
const char *name :中断的描述
参数5:
void *dev 传入设备结构体指针,用于记录和调试
返回值:
成功返回0,失败返回负值错误码
1. 检查中断是否已经申请:如果已经申请,直接返回错误。
2. 建立中断描述符:为该中断号创建中断描述符irq_desc,保存相关的处理函数、触发类型、设备信息等。
3. 注册中断:向中断控制器注册该中断服务程序,通常会指定触发条件、优先级等信息。并使能相应的中断。当中断触发后,CPU会调用注册的中断服务程序,通过irq_desc找到相关的处理函数来服务中断。
typedef irqreturn_t (*irq_handler_t)(int, void *);
该函数有两个参数,int 和void *;
int :相应的中断号
void *:需要与request_irq函数的参数dev保持一致,用于区分共享中断的不同设备(dev也可以指向设备数据结构)
返回值:
IRQ_HANDLED:表示中断已被正常处理。内核会继续处理中断返回后的操作。
IRQ_NONE:表示中断未被处理。内核将该中断标记为factual,并尝试交由其他注册的中断处理程序处理,或采取默认处理方法。
一般我们的中断处理程序处理了中断请求后应返回IRQ_HANDLED。只有在确认该中断不是我们服务的中断时才返回IRQ_NONE。
void free_irq(unsigned int irq,void * dev_id);
参数:
irq:要释放的中断号
dev_id: request_irq时传入的dev参数,用于匹配确认正确的中断
- struct KeyNode
- {
- unsigned int major;
- struct class * cls;
- struct device * dev;
- unsigned int irqno;
- unsigned int * dat;
- int value;
- wait_queue_head_t head;
- unsigned int key_state;
- };
- struct KeyNode key;
- ssize_t key_read (struct file * filep, char __user * buf, size_t size, loff_t *fops)
- {
- printk("-------%s--------\n",__FUNCTION__);
- if(filep->f_flags & O_NONBLOCK && !key.key_state)//非阻塞
- {
- return -EAGAIN;
- }
-
-
- wait_event_interruptible(key.head,key.key_state);//阻塞
-
- copy_to_user(buf,&(key.value),size);
-
- key.key_state = 0;
-
- return 4;
- }
-
- int key_open (struct inode * inode, struct file * filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
- int key_release (struct inode * inode, struct file *filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
-
- const struct file_operations fops = {
-
- .release = key_release,
- .open = key_open,
- .read = key_read,
-
-
- };
-
- static int __init key_drv_init(void)
- {
-
- key.major = register_chrdev(0,"key drv",&fops);
-
- key.cls = class_create(THIS_MODULE,"key cls");
-
- key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
-
- struct device_node * np = of_find_node_by_path("/key_int_node");
-
- key.irqno = irq_of_parse_and_map(np,0);
-
- request_irq(key.irqno,irq_handler,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"key irq",NULL);
-
- //创建等待队列
- init_waitqueue_head(&(key.head));
-
- key.dat = ioremap(0x11000c24,4);
-
- return 0;
- }
- //中断处理函数----irqno
- irqreturn_t irq_handler(int irqno, void * dev)
- {
- printk("hello world\n");
-
- key.value = readl(key.dat) >> 2 & 1;
- key.key_state = 1;
- //唤醒
- wake_up_interruptible(&(key.head));
-
- return IRQ_HANDLED;
- }
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
-
- int main()
- {
- int i = -2;
- int fd = open("/dev/key3",O_RDONLY|O_NONBLOCK);
- /*
- int status = fcntl(fd,F_GETFL);
- status = status | O_NONBLOCK;
- fcntl(fd,F_SETFL,status);
- */
- while(1)
- {
- read(fd,&i,4);
- printf("%d\n",i);
- }
-
- close(fd);
- return 0;
- }
阻塞等同于休眠,当进程在读取外部设备的资源(数据),如果资源没有准备,就会进行休眠等待
根据等待队列头创建等待队列
init_waitqueue_head(wait_queue_head_t * q);
wait_queue_head_t是一个等待队列结构体,包含了一个等待队列的相关信息,如:
- 等待队列中的任务链表
- 等待队列的锁(用于多线程同步访问)
- 等待队列的计数器(记录当前等待队列内的任务数)init_waitqueue_head函数初始化一个wait_queue_head_t类型的等待队列,具体工作如下:
1. 将等待队列头的任务链表初始化为空。
2. 初始化等待队列头的锁。
3. 将等待队列头的计数器重置为0。
4. 如果该等待队列头是一般等待队列(WQ_FLAG_EXCLUSIVE未置位),还需要初始化其万能链表。简而言之,init_waitqueue_head函数主要对一个等待队列头的成员进行初始化,以便其能够正常运作。在驱动开发中,我们通常在模块加载时调用此函数对需要的等待队列头进行初始化。
- wait_event_interruptible(wait_queue_head_t wq,condition)
- 用于将当前任务添加到一个等待队列中并睡眠,直到 condition 为真或被信号唤醒
- 参数1:
- wait_queue_head_t wq:等待队列头
- 参数2:
- condition:条件,为假,就会等待;为真,就不会等待
wait_event_interruptible的主要工作流程如下:
1. 检查condition,如果为真则直接返回0。
2. 否则,将当前任务添加到等待队列wq的等待链表中。
3. 使当前任务睡眠,调度器选择其他任务运行。
4. 如果当前任务被信号唤醒,则从等待队列中移除任务,返回-ERESTARTSYS。
5. 当condition为真时,等待队列中的任务将被唤醒。等待队列将从链表中移除当前任务,并返回0。
6. 步骤1~5会重复,直到condition为真或被信号唤醒退出循环。
简而言之,wait_event_interruptible函数用于实现条件睡眠,当前任务会一直睡眠直到condition满足或收到信号。它与等待队列结合,可以用于进程间同步与通信。
- //示例代码
- wait_queue_head_t wait_queue;
- int condition = 0;
-
- // 初始化等待队列头
- init_waitqueue_head(&wait_queue);
-
- // 休眠任务,等待condition为真
- wait_event_interruptible(wait_queue, condition);
-
- // 条件满足,唤醒等待队列中的任务
- condition = 1;
- wake_up(&wait_queue);
- wake_up_interruptible(wait_queue_head_t * q);
- *q:要唤醒的队列头
wake_up_interruptible的主要工作流程如下:
1. 启用等待队列头q的锁,以保证多线程安全访问。
2. 遍历等待队列头q中的等待任务链表。
3. 对于每个任务,执行唤醒操作try_to_wake_up。
4. try_to_wake_up函数检查任务状态,如果是可中断睡眠(TASK_INTERRUPTIBLE)则将其唤醒。
5. 唤醒的任务将从等待队列头q的等待链表中移除。
6. 禁用等待队列头q的锁,退出流程。
简而言之,wake_up_interruptible函数会尝试唤醒等待队列q中的所有处于可中断睡眠状态(TASK_INTERRUPTIBLE)的任务。被唤醒的任务将从等待队列的等待链表中移除,恢复运行。
在读写的时候,如果没有数据,就立即返回,应用需要设置为非阻塞
- int fd = open("/dev/key3",O_RDONLY|O_NONBLOCK);
- int status = fcntl(fd,F_GETFL);status = status | O_NONBLOCK;
- fcntl(fd,F_SETFL,status);
驱动中需要区分当前模式是否为非阻塞模式:如果是非阻塞模式,且没有数据就立即返回
- if(filep->f_flags & O_NONBLOCK && !key.key_state)//非阻塞
- {
- return -EAGAIN;
- }
- fcntl函数用于操控文件描述符,它的原型为:
- int fcntl(int fd, int cmd, ...);
- 其主要参数为:
- - fd:文件描述符。
- - cmd:操作命令,指定对文件描述符fd进行的控制操作。
- - ...:cmd所需要的其他参数,取决于所指定的操作命令。
- 当cmd为F_GETFL时,fcntl函数的作用是获取文件描述符fd的文件状态标志。它不需要第三个参数,函数调用为:
- int status = fcntl(fd, F_GETFL);
- 该调用将返回fd文件的状态标志,通常存储在status变量中。状态标志是用于指定文件的属性和操作方式的一组宏定义,包括:
- - O_RDONLY: 只读文件
- - O_WRONLY: 只写文件
- - O_RDWR: 读写文件
- - O_APPEND: 追加模式
- - O_NONBLOCK: 非阻塞I/O
- - O_SYNC: 同步I/O
- 等等。
- 这些状态标志可以在打开文件时通过flags参数设置,也可以通过fcntl函数的F_SETFL操作在打开后动态修改。
- 例如,获取标准输入stdin的状态标志:
-
- int status = fcntl(STDIN_FILENO, F_GETFL);
- 设置文件描述符fd为非阻塞I/O:
-
- int flags = fcntl(fd, F_GETFL); // 获取原状态标志
- flags |= O_NONBLOCK; // 设置非阻塞标志
- fcntl(fd, F_SETFL, flags); // 设置状态标志
当有数据的时候,驱动就会发送信号给(SIGIO)给应用,应用就可以异步去读写数据,不用主动读写。
驱动用于发送信号,和进程进行关联,也就是信号发送给谁,通过fasync接口实现
- int key_fasync (int fd, struct file * filep, int on)
- {
- return fasync_helper(fd,filep,on,&(key.fasync));
- }
- int key_fasync(int fd, struct file *filep, int on);
- 参数:
- fd:文件描述符。
- filep:与文件描述符fd对应文件的file结构体指针。
- on:开启或取消asynchronous notification。
- 功能:
- 使用key_fasync设置异步通知后,应用程序无需轮询文件描述符,内核会在I/O完成后主动发送信号通知应用程序,从而提高程序的效率与响应速度。
-
- 返回值:
- 0:操作成功完成。
- 负值:操作失败,此时不会有任何异步通知的设置或取消动作。
- 可能的错误码有:
- - -EBADF:文件描述符fd无效。
- - -EINVAL:filp结构指针filep为空或filp与fd不 match。
- - -ENOMEM:内存分配失败。fasync_list链表使用kmalloc动态分配,如果分配失败将返回此错误。
-
- 详细作用描述:
- 当on为真时,key_fasync函数的作用是:
- 1. 根据文件描述符fd查找其对应的filp(file结构体指针),通常由filep参数提供。
- 2. 将该filp添加到fasync_list链表,该链表包含所有请求异步通知的filp。
- 3. 当文件可读、写或出错时,内核会遍历fasync_list链表,调用程序注册的信号驱动函数,发送相应的信号给进程。
- 4. 从而实现I/O完成后立即通知应用程序的效果。
- 当on为假时,key_fasync函数的作用是:
- 1. 从fasync_list链表中移除与文件描述符fd对应的filp。
- 2. 取消该文件描述符的异步通知请求。
- //然后在某个特定的时刻(比如有数据的时候)发送信号
- kill_fasync(&(key.fasync),SIGIO,POLLIN);
- void kill_fasync(struct fasync_struct **fp, int sig, int band);
- 其主要参数为:
- - fp:fasync_struct结构体指针的指针,指向I/O通知链表。
- - sig:要发送的信号,一般为SIGIO。
- - band:I/O操作的类型,如POLLIN、POLLOUT等。
-
- 功能:
- kill_fasync函数根据 band参数检查链表fp上的每个文件描述符是否请求了相应的I/O监视,如果是则发送sig信号通知应用进程相应的I/O事件已就绪。
-
- kill_fasync函数的主要工作流程如下:
- 1. 遍历I/O通知链表fp所指向的fasync_struct结构体链表。
- 2. 对于每个fasync_struct,检查其文件描述符的I/O监视事件是否包含band指定的I/O操作。
- - 如果不包含,继续遍历下一结构体。
- - 如果包含,构造siginfo结构体,发送sig信号给fasync_struct->fa_file->f_owner所指定的进程。
- 3. 发送信号通知应用程序,I/O操作band已就绪。
- 4. 退出,异步通知完成。
- 1、设置信号怎么处理
- signal(SIGIO,catch_signal);
- void catch_signal(int signo)
- {
- if(signo == SIGIO)
- {
- int value = -1;
- read(fd,&value,4);
- printf("program data is %d\n",value);
- }
- }
- 2、将当前进程设置为SIGIO的属主进程
- //设置当前进程为信号的属主进程
- fcntl(fd,F_SETOWN,getpid());
- 3、将io模式设置为异步模式
-
- int stats = fcntl(fd,F_GETFL);
- stats = stats | FASYNC;
- fcntl(fd,F_SETFL,stats);
- //key_test_tasklet.c
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <signal.h>
-
-
- int fd;
-
- void catch_signal(int signo)
- {
-
- if(signo == SIGIO)
- {
- int value = -1;
- read(fd,&value,4);
- printf("program data is %d\n",value);
- }
-
- }
-
- int main()
- {
- int i = -2;
- fd = open("/dev/key3",O_RDONLY);
-
- //信号处理方式
- signal(SIGIO,catch_signal);
-
- //设置当前进程为信号的属主进程
- fcntl(fd,F_SETOWN,getpid());
-
- //将io模式设置为异步模式
- int stats = fcntl(fd,F_GETFL);
- stats = stats | FASYNC;
- fcntl(fd,F_SETFL,stats);
-
- while(1)
- {
- printf("hello world\n");
- sleep(1);
- }
-
- close(fd);
- return 0;
- }
-
-
-
- //key_drv_tasklet.c
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/of.h>
- #include <linux/of_irq.h>
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include <linux/fs.h>
- #include <linux/device.h>
-
- #include <asm/io.h>
- #include <asm/poll.h>
- #include <asm/uaccess.h>
- #include <linux/wait.h>
- #include <linux/sched.h>
-
- struct KeyNode
- {
- unsigned int major;
- struct class * cls;
- struct device * dev;
- unsigned int irqno;
- unsigned int * dat;
- int value;
- wait_queue_head_t head;
- unsigned int key_state;
- struct fasync_struct * fasync;
- struct tasklet_struct tasklet;
- };
-
- struct KeyNode key;
-
- ssize_t key_read (struct file * filep, char __user * buf, size_t size, loff_t *fops)
- {
- printk("-------%s--------\n",__FUNCTION__);
- if(filep->f_flags & O_NONBLOCK && !key.key_state)//非阻塞
- {
- return -EAGAIN;
- }
-
-
- wait_event_interruptible(key.head,key.key_state);//阻塞
-
- copy_to_user(buf,&(key.value),size);
-
- key.key_state = 0;
-
- return 4;
- }
-
- int key_open (struct inode * inode, struct file * filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
- int key_release (struct inode * inode, struct file *filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
-
- int key_fasync (int fd, struct file * filep, int on)
- {
-
- return fasync_helper(fd,filep,on,&(key.fasync));
-
- }
-
- const struct file_operations fops = {
-
- .release = key_release,
- .open = key_open,
- .read = key_read,
- .fasync = key_fasync,
-
- };
-
- //中断下半部分执行操作
- void irq_func(unsigned long data)
- {
-
- printk("data is %d\n",data);
-
- }
-
-
- //中断处理函数----irqno
- irqreturn_t irq_handler(int irqno, void * dev)
- {
- printk("hello world\n");
-
- key.value = readl(key.dat) >> 2 & 1;
- key.key_state = 1;
- //唤醒
- wake_up_interruptible(&(key.head));
-
- //发送信号
- kill_fasync(&(key.fasync),SIGIO,POLLIN);
-
- //中断下半部分启动--放入内核线程
- tasklet_schedule(&(key.tasklet));
-
- return IRQ_HANDLED;
- }
-
-
- static int __init key_drv_init(void)
- {
-
- key.major = register_chrdev(0,"key drv",&fops);
-
- key.cls = class_create(THIS_MODULE,"key cls");
-
- key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
-
- struct device_node * np = of_find_node_by_path("/key_int_node");
-
- key.irqno = irq_of_parse_and_map(np,0);
-
- request_irq(key.irqno,irq_handler,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"key irq",NULL);
-
- //初始化中断下半部分任务(struct tasklet_struct 结构体初始化)
- tasklet_init(&(key.tasklet),irq_func,45);
-
- //创建等待队列
- init_waitqueue_head(&(key.head));
-
- key.dat = ioremap(0x11000c24,4);
-
- return 0;
- }
-
- static void __exit key_drv_exit(void)
- {
-
- }
-
- module_init(key_drv_init);
- module_exit(key_drv_exit);
- MODULE_LICENSE("GPL");
当中断处理程序比较长时,为了提高相应特性,linux内核处理中断的方案是:人为地将处理程序分为两部分,即中断上半部、中断下半部。其中中断上半部标记中断,调度下半部;下半部负责真正的操作(比如读取按键键值、从网卡读取缓冲数据等)
内部实现调用sortirq
sorfirq:处理速度比较快,但是是内核级别的机制,要修改内核源码,不推荐使用
tasklet在中断上下文执行,因此不能被阻塞,不能睡眠,不能被打断。
编程:
1. 初始化,设置tasklet任务结构体
struct tasklet_struct tasklet;
tasklet_init(&任务结构体的地址,中断下半部分执行函数,函数参数);
2. 启动中断下半部分----把任务放入到内核线程中
//中断下半部分启动--放入内核线程
tasklet_schedule(&tasklet);
workqueue在进程上下文,可以被重新调度,可以阻塞,也可以睡眠。
编程:
1. 初始化,设置workqueue任务结构体
struct work_struct workqueue;
INIT_WORK(&任务结构体的地址,中断下半部分执行函数);
2. 启动中断下半部分----把任务放入到内核线程中
//中断下半部分启动--放入内核线程
schedule_work(&workqueue);
- //key_drv_queue.c
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/of.h>
- #include <linux/of_irq.h>
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include <linux/fs.h>
- #include <linux/device.h>
-
- #include <asm/io.h>
- #include <asm/poll.h>
- #include <asm/uaccess.h>
- #include <linux/wait.h>
- #include <linux/sched.h>
-
- struct KeyNode
- {
- unsigned int major;
- struct class * cls;
- struct device * dev;
- unsigned int irqno;
- unsigned int * dat;
- int value;
- wait_queue_head_t head;
- unsigned int key_state;
- struct fasync_struct * fasync;
- struct work_struct work;
- };
-
- struct KeyNode key;
-
- ssize_t key_read (struct file * filep, char __user * buf, size_t size, loff_t *fops)
- {
- printk("-------%s--------\n",__FUNCTION__);
- if(filep->f_flags & O_NONBLOCK && !key.key_state)//非阻塞
- {
- return -EAGAIN;
- }
-
-
- wait_event_interruptible(key.head,key.key_state);//阻塞
-
- copy_to_user(buf,&(key.value),size);
-
- key.key_state = 0;
-
- return 4;
- }
-
- int key_open (struct inode * inode, struct file * filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
- int key_release (struct inode * inode, struct file *filep)
- {
- printk("-------%s--------\n",__FUNCTION__);
- return 0;
- }
-
-
- int key_fasync (int fd, struct file * filep, int on)
- {
-
- return fasync_helper(fd,filep,on,&(key.fasync));
-
- }
-
- const struct file_operations fops = {
-
- .release = key_release,
- .open = key_open,
- .read = key_read,
- .fasync = key_fasync,
-
- };
-
- //中断下半部分执行操作
- void work_func(struct work_struct *work)
- {
-
- printk("nihao\n");
-
- }
-
- //中断处理函数----irqno
- irqreturn_t irq_handler(int irqno, void * dev)
- {
- printk("hello world\n");
-
- key.value = readl(key.dat) >> 2 & 1;
- key.key_state = 1;
- //唤醒
- wake_up_interruptible(&(key.head));
-
- //发送信号
- kill_fasync(&(key.fasync),SIGIO,POLLIN);
-
- //中断下半部分启动--放入内核线程(不是在中断中执行任务,而是把任务加入到内核,在内核执行)
- schedule_work(&(key.work));
-
- return IRQ_HANDLED;
- }
-
-
- static int __init key_drv_init(void)
- {
-
- key.major = register_chrdev(0,"key drv",&fops);
-
- key.cls = class_create(THIS_MODULE,"key cls");
-
- key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
-
- struct device_node * np = of_find_node_by_path("/key_int_node");
-
- key.irqno = irq_of_parse_and_map(np,0);
-
- request_irq(key.irqno,irq_handler,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"key irq",NULL);
-
-
- //初始化中断下半部分任务(struct work_struct 结构体初始化)
- INIT_WORK(&(key.work),work_func);
-
- //创建等待队列
- init_waitqueue_head(&(key.head));
-
- key.dat = ioremap(0x11000c24,4);
-
- return 0;
- }
-
- static void __exit key_drv_exit(void)
- {
-
- }
-
- module_init(key_drv_init);
- module_exit(key_drv_exit);
- MODULE_LICENSE("GPL");
- //key_test_queue.c
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <signal.h>
-
-
- int fd;
-
- void catch_signal(int signo)
- {
-
- if(signo == SIGIO)
- {
- int value = -1;
- read(fd,&value,4);
- printf("program data is %d\n",value);
- }
-
- }
-
- int main()
- {
- int i = -2;
- fd = open("/dev/key3",O_RDONLY);
-
- //信号处理方式
- signal(SIGIO,catch_signal);
-
- //设置当前进程为信号的属主进程
- fcntl(fd,F_SETOWN,getpid());
-
- //将io模式设置为异步模式
- int stats = fcntl(fd,F_GETFL);
- stats = stats | FASYNC;
- fcntl(fd,F_SETFL,stats);
-
- while(1)
- {
- printf("hello world\n");
- sleep(1);
- }
-
- close(fd);
- return 0;
- }
-
-
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。