前言
上一篇文章介绍了一下字符设备编写的步骤,仅此而已,想要真正掌握字符设备编写,需要对其包含的数据结构有所掌握,这才是重要的。
一、字符设备的数据结构
图片来源于网络,一直以来有想自己画一下,以后有时间将上图换成我自己画的。
二、数据结构分析
编写字符设备需要实现file_operations接口,在APP打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP的每一个文件句柄,在内核里面都有一个“ struct file”与之对应。
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; #define f_dentry f_path.dentry struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra;
open 打开文件时,传入的 flags、 mode 等参数会被记
录在内核中对应的 struct file 结构体里(f_flags,f_mode)去读写文件时,文件的当前偏移地址也会保存在 struct file 结构体的f_pos 成员里。打开字符设备节点时,内核中也有对应的 struct file。这个结构体中的结构体:
struct file_operations *f_op;
这是由驱动程序提供的。
总结
字符设备就暂时学习到这,后面将继续为大家分享。
附录
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/slab.h> /* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004) /* 映射后的寄存器虚拟地址指针 */ static volatile unsigned int *IMX6U_CCM_CCGR1; static volatile unsigned int *SW_MUX_GPIO1_IO03; static volatile unsigned int *SW_PAD_GPIO1_IO03; static volatile unsigned int *GPIO1_DR; static volatile unsigned int *GPIO1_GDIR; //静态指定 static unsigned int dev_major; static struct class *cls; static struct device *dev; /* 映射后的寄存器虚拟地址指针 */ static int kernel_val = 555; //read(fd,buf,size); ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops) { int ret; printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); ret = copy_to_user(buf, &kernel_val, count); if(ret > 0) { printk("failed copy_to_user\r\n"); return -EFAULT; } return 0; } ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops) { int ret; int value; printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); ret = copy_from_user(&value, buf, count); if(ret > 0) { printk("failed copy_from_user\r\n"); return -EFAULT; } if(value){ *GPIO1_DR |= (1 << 3); }else{ *GPIO1_DR &= ~(1 << 3); } return 0; } int chr_drv_open (struct inode *inode, struct file *filp) { printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } int chr_drv_close (struct inode *inode, struct file *filp) { printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } const struct file_operations my_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, }; /* ②在模块加载入口函数中 a 申请主设备号(内核中用于区分和管理不同字符设备) register_chrdev() b 创建设备节点文件(为用户提供一个可操作到文件接口--open()) struct class *class_create() struct device *device)create() c 硬件初始化 1.地址映射 2.中断申请 3.实现硬件的寄存器的初始化 d 实现file_operation */ static int __init chr_dev_init(void) { int ret; /* //0 实例化全局的设备对象 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL); { printk("kmalloc failed\r\n"); return -ENOMEM; } */ //一般都是申请设备号资源 //1.申请设备号 dev_major = register_chrdev(0, "chr_dev_test", &my_fops); if(dev_major < 0){ printk(KERN_ERR"register_chrdev failed\n"); return -ENODEV; } //2.创建设备文件 cls = class_create(THIS_MODULE, "chr_cls"); if(IS_ERR(cls)) { printk(KERN_ERR"class_create failed\n"); ret = PTR_ERR(cls);//将指针出错原因转换为一个出错码 goto err_1; } // /dev/led0 dev = device_create(cls, NULL, MKDEV(dev_major, 0), NULL, "led%d", 0); if(IS_ERR(dev)) { printk(KERN_ERR"device_create failed\n"); ret = PTR_ERR(dev);//将指针出错原因转换为一个出错码 goto err_2; } //3,硬件初始化 /* 初始化LED */ /* 1、寄存器地址映射 */ IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); /* enable GPIO1 * configure GPIO1_io3 as gpio * configure GPIO1_io3 as output */ *IMX6U_CCM_CCGR1 |= 0x0C000000; *SW_MUX_GPIO1_IO03 &= 0xfffffff0; *SW_MUX_GPIO1_IO03 |= 0x5; *SW_PAD_GPIO1_IO03 &= 0xfffffff0; *SW_PAD_GPIO1_IO03 |= 0x10B0; *GPIO1_GDIR &= 0xfffffff0; *GPIO1_GDIR |= 0x8; /* readl writel static inline u32 readl(const volatile void __iomem *addr) static inline void writel(u32 value, void *addr) 2、使能GPIO1时钟 val = readl(IMX6U_CCM_CCGR1); val &= ~(3 << 26); 清楚以前的设置 val |= (3 << 26); 设置新值 writel(val, IMX6U_CCM_CCGR1); 3、设置GPIO1_IO03的复用功能,将其复用为 GPIO1_IO03,最后设置IO属性。 writel(5, SW_MUX_GPIO1_IO03); 寄存器SW_PAD_GPIO1_IO03设置IO属性 bit 16:0 HYS关闭 bit [15:14]: 00 默认下拉 bit [13]: 0 kepper功能 bit [12]: 1 pull/keeper使能 bit [11]: 0 关闭开路输出 bit [7:6]: 10 速度100Mhz bit [5:3]: 110 R0/6驱动能力 bit [0]: 0 低转换率 writel(0x10B0, SW_PAD_GPIO1_IO03); 4、设置GPIO1_IO03为输出功能 val = readl(GPIO1_GDIR); val &= ~(1 << 3); 清除以前的设置 val |= (1 << 3); 设置为输出 writel(val, GPIO1_GDIR); 5、默认关闭LED val = readl(GPIO1_DR); val |= (1 << 3); writel(val, GPIO1_DR); 或 writel(readl(led_dev->GPIO1_DR) | (1 << 3),led_dev->GPIO1_DR); */ return 0; err_2: class_destroy(cls); err_1: unregister_chrdev(dev_major, "chr_dev_test"); return ret; } static void __exit chr_dev_exit(void) { //一般都是释放资源 /* 取消映射 */ iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); device_destroy(cls, MKDEV(dev_major, 0)); class_destroy(cls); unregister_chrdev(dev_major, "chr_dev_test"); } //①实现模块的加载和卸载入口函数 module_init(chr_dev_init); module_exit(chr_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Lfp");