【学习笔记】嵌入式linux驱动——驱动模型1——字符驱动模型

2019-07-12 18:08发布

在学习字符驱动模型之前,一些关于环境搭建,与最基本的linux驱动框架建议参考这篇文章: 环境搭建、linux驱动基本框架 以下主要包括字符驱动和上位机程序部分

(一)字符驱动模型

关于字符驱动模型,简单提点一下,以实战为主。 驱动原理图: 从结构上来看,字符驱动分为两部分:

第一部分,标准的驱动入口

有一般linux驱动的标准特点,整体入口在module_init、module_exit: static int memdev_init(void){ int ret = -1; /*动态分配设备号*/ ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev"); if (ret >= 0){ cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/ cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/ printk(KERN_ALERT "init_success "); } else { printk(KERN_ALERT "init_error "); } return ret; } static void memdev_exit(void){ cdev_del(&my_dev.cdev); unregister_chrdev_region(my_dev.devno,1); printk(KERN_ALERT "exit_my_led "); } module_init(memdev_init); module_exit(memdev_exit); 以上以module_init、module_exit作为入口,把memdev_init等两个函数分别传进去作为真实调用的初始化函数。关于内部一些添加设备和动态、静态分配设备号的方法及其原理不再赘述。

第二部分,设备描述符、设备号

设备描述符:struct cdev 设备号:dev_t 都需要单独定义。 设备描述符struct cdev和设备号dev_t在第一部分中的设备初始化函数里面使用,用于添加字符设备: cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/ 实际上设备号是结构体struct cdev(设备描述符)内的一个成员,外部定义的设备号在alloc_chrdev_region函数里面动态分配后,再通过cden_add传回cdev内部那个设备号里面。差不多有一些中间量的意思。 此外还需要定义、开辟一个全局内存。例程里面把他们全都放进一个结构体里面: #define MEM_SIZE 1024 struct mem_dev{ struct cdev cdev; int mem[MEM_SIZE];//全局内存4k dev_t devno; }; struct mem_dev my_dev;

第三部分,操作函数集——file_operations结构体

简单的操作函数集定义: const struct file_operations mem_ops = { .llseek = mem_llseek, .open = mem_open, .read = mem_read, .write = mem_write, .release = mem_release, }; 当然,完整的操作函数集可不止这么一点,但初学入门这点就足够了。 关于操作函数集,这里需要提一个概念,字符驱动他的机制是这样的:应用程序、设备驱动程序之间,是隔着一个字符设备文件的,你使用read、write这些函数对下位进行各种协议(uart等)通讯时,你读的是这个字符设备文件,一般在/dev/xxx这里。但是内核接收到你这个read、write函数会自动调用操作函数集里面指向的函数,比如上面这个就是指向men_write。 应用程序——>字符设备文件——>设备驱动程序 open:打开文件 release:关闭文件 read:读文件 write:写文件 llseek:移动读写文件指针 这部分剩下的就是对各个文件操作函数进行实例化编写。

第二部分、第三部分耦合

显然,第二部分和第三部分被明显区分开来了,目前来说没有发现耦合关系,那么实际上他么是怎么进行耦合的呢? 可以在模块的初始化memdev_init函数处看到: cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/ cdev_init函数把他们耦合起来了!

编译

对于Ubuntu18.04 LTS来说,默认已经有modules了,不需要额外搭建环境,makefile如下: obj-m:=my_led.o KERNELDIR:=/lib/modules/4.15.0-45-generic/build PWD:=$(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.orde

安装模块

sudo insmod my_led.ko
lsmod  #查看是否加载好
cat /proc/devices    #查看自动分配的号码
sudo mknod /dev/my_led_memdev0 c 243 0 #其中243是上面自动分配的号码 控制台通常是打印不出信息,要从这里查看系统log: sudo tail /var/log/syslog

卸载模块

sudo rm /dev/my_led_memdev0
sudo rmmod my_led

一些问题

在Ubuntu18.04 LTS下面编译其内核对应的字符驱动,会出现copy_to_user和copy_from_user两个函数的错误提示,但在交叉编译nanopc t4的驱动程序并没有。 对于Ubuntu18.04 LTS我把copy_to_user和copy_from_user改成了raw_copy_to_user和raw_copy_from_user,运行正常。看样子是这个内核把这两个函数改了?

(二)应用程序

一开始我在网络上copy了一份例程,略作修改,但无论怎么运行都没办法达到预期期望: #include #include #include #include #include int main(int argc, char **argv) { int fd=0; int val=1; char buf_write[5]="only"; char buf_my[10]={0}; fd=open("/dev/my_led_memdev0",O_RDWR); if(fd<0) printf("can't open! "); else { write(fd, buf_write, 4); read(fd, buf_my, 4); printf("%s", buf_my); } printf("end "); return 0; } 后来仔细探索,发现write和read的读写,是从文件指针loff_t开始的,write调用以后,loff_t文件指针就指向了最后一位,直接读取是没办法读到刚才写进去的数据。 我不知道这份例程的作者有没有运行过他的代码,甚至很多例程都是这样写的。我不清楚是不是驱动函数的策略不同或者是内核不一样的缘故,但其中绝对有不少实在误人子弟! #include #include #include #include #include int main(int argc, char **argv) { int fd=0; int val=1; char buf_write[5]="wond"; char buf_my[10]={0}; fd=open("/dev/my_led_memdev0",O_RDWR); if(fd<0) printf("can't open! "); else { lseek(fd, 0, SEEK_SET); write(fd, buf_write, 4); lseek(fd, 0, SEEK_SET); read(fd, buf_my, 8); printf("read:%s ", buf_my); } printf("end "); return 0; }

附上驱动代码:

#include #include #include #include #include #define MEM_SIZE 1024 MODULE_LICENSE("GPL"); struct mem_dev{ struct cdev cdev; int mem[MEM_SIZE];//全局内存4k dev_t devno; }; struct mem_dev my_dev; /*打开设备*/ int mem_open(struct inode *inode, struct file *filp){ int num = MINOR(inode->i_rdev);/*获取次设备号*/ printk(KERN_ALERT "open:my_led_memdev "); if(num == 0){/*判断为那个设备*/ filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/ } return 0; } /*文件关闭函数*/ int mem_release(struct inode *inode, struct file *filp){ return 0; printk(KERN_ALERT "close:my_led_memdev "); } static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){ int * pbase = filp -> private_data;/*获取数据地址*/ unsigned long p = *ppos;/*读的偏移*/ unsigned int count = size;/*读数据的大小*/ int ret = 0; printk(KERN_ALERT "read:ready to my_led_memdev "); if(p >= MEM_SIZE)/*合法性判断*/ return 0; if(count > MEM_SIZE - p)/*读取大小修正*/ count = MEM_SIZE - p; if(raw_copy_to_user(buf,pbase + p,size)){ ret = - EFAULT; }else{ *ppos += count; ret = count; } printk(KERN_ALERT "read:my_led_memdev "); return ret; } static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos){ unsigned long p = *ppos; unsigned int count = size; int ret = 0; int *pbase = filp -> private_data; if(p >= MEM_SIZE) return 0; if(count > MEM_SIZE - p) count = MEM_SIZE - p; if(raw_copy_from_user(pbase + p,buf,count)){ ret = - EFAULT; }else{ *ppos += count; ret = count; } printk(KERN_ALERT "write:my_led_memdev "); return ret; } /*seek文件定位函数*/ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence){ loff_t newpos; switch(whence) { case SEEK_SET:/*从文件头开始定位*/ newpos = offset; break; case SEEK_CUR:/*从当前位置开始定位*/ newpos = filp->f_pos + offset; break; case SEEK_END: newpos = MEM_SIZE * sizeof(int)-1 + offset;/*从文件尾开始定位*/ break; default: return -EINVAL; } if ((newpos<0) || (newpos>MEM_SIZE * sizeof(int)))/*检查文件指针移动后位置是否正确*/ return -EINVAL; printk(KERN_ALERT "llseek:my_led_memdev "); filp->f_pos = newpos; return newpos; } const struct file_operations mem_ops = { .llseek = mem_llseek, .open = mem_open, .read = mem_read, .write = mem_write, .release = mem_release, }; static int memdev_init(void){ int ret = -1; /*动态分配设备号*/ ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev"); if (ret >= 0){ cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/ cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/ printk(KERN_ALERT "init_success "); } else { printk(KERN_ALERT "init_error "); } return ret; } static void memdev_exit(void){ cdev_del(&my_dev.cdev); unregister_chrdev_region(my_dev.devno,1); printk(KERN_ALERT "exit_my_led "); } module_init(memdev_init); module_exit(memdev_exit); 好好学习,天天向上