首页 > 编程知识 正文

linux设备驱动开发,友善之臂论坛

时间:2023-05-06 13:53:13 阅读:164568 作者:3639

驱动程序是一种特殊的程序,它允许计算机与设备进行通信,相当于内核和硬件之间的接口,操作系统仅通过该接口来禁止硬件设备的操作驱动程序接受上级软件(APP应用程序、内核)的请求,完成对硬件的操作,切断了硬件的详细内容。 Linux平台的驱动程序将硬件设备抽象为一个文件,并通过APP处理文件来操作硬件。

1 linux驱动程序分类Linux系统的驱动程序有字符设备驱动程序、块设备驱动程序、网络设备驱动程序3种,以下分别对3种驱动程序进行说明。

1.1字符设备驱动字符设备是访问时没有高速缓存的设备,是指通过字符流方式进行访问的设备。 这种设备支持按字节/字符顺序读写数据,通常不支持随机访问。 典型的字符设备可以是led、key、camera、显卡、串行端口等。

1.2块设备驱动器与驱动器之间的数据交互以块为单位,APP应用程序可以随机访问设备数据,程序可以自行决定读取数据的位置。 典型的块设备包括u盘、eMMC和SD卡,它们允许APP应用程序在磁盘上的任意位置搜索和读取数据。

1.3网络设备驱动并访问分组传输方法的设备与上述不同,是用ifconfig创建设备而构成的。 网络驱动程序和块驱动程序的最大区别在于网络驱动程序异步接收外部数据,而块驱动程序只响应内核请求。 典型的网络设备驱动程序包括蓝牙、wifi、网卡等。

2 Linux字符驱动开发框架Linux内核使用cdev结构来描述字符设备。 此结构的定义是include/linux/cdev.h文件,cdev结构的成员如下:

struct cdev {struct kobject kobj; 结构模块* owner; //指向字符设备所在的内核模块的对象指针const struct file_operations *ops; //该结构是字符设备可以实现的方法struct list_head list; dev_t dev; //字符设备的设备编号。 用主设备编号和辅助设备编号构成unsigned int count; //属于同一主站编号的从站编号的个数; 其中最重要的是一个名为file_operations的结构,每个设备驱动程序实现了在该结构中定义的部分或全部函数,这取决于内核file_operations的定义版本。 linux 3.6中的定义如下。

struct file _ operations { struct module * owner; loff_t(*llseek ) ) struct file *,loff_t,int; size_t(*read ) ) struct file *,char __user *,size_t,loff_t * ); size_t(*write ) ) struct file *、const char __user *、size_t、loff_t * ); size_t(AIO_read ) ) struct kiocb *、const struct iovec *、unsigned long、loff_t ); size_t(*AIO_write )、struct kiocb *、const struct iovec *、unsigned long、loff_t ); int(*readdir ) ) struct file *、void *、filldir_t ); unsignedint(*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 ) )结构节点*,结构文件*; int(*flush ) ) struct file *,fl_owner_t id ); int(*release ) )结构节点*,结构文件*; 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 * ); size_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,uns

igned 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);};

当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。因此,驱动开发就是相当于在实现驱动的入口和出口函数基础上再把file_operations结构体中的函数实现,入口、出口函数在驱动模块加载和卸载时调用,file_operations结构体中的函数在设备控制时调用。

3 Linux字符驱动实例

在linux源码drivers/char目录下新建first_driver.c文件,文件中内容如下所示:

#include <linux/fs.h>#include <linux/module.h>#include <linux/init.h>#include <linux/device.h>#include <linux/uaccess.h>#include <linux/types.h>#include <asm/io.h>static struct class *first_drv_class;static struct device *first_drv_class_dev;static dev_t devno; //设备号static int major = 231; //主设备号static int minor = 0; //次设备号static char *module_name = "first_driver"; //模块名static int first_drv_open(struct inode *inode, struct file *file){printk("first_drv_openn");return 0;}static int first_drv_write(struct file *file, const char __user *buf, size_tcount, loff_t *ppos){printk("first_drv_writen");return 0;}static struct file_operations first_drv_fops ={.owner = THIS_MODULE,// 将对应的函数关联在file_operations的结构体中.open = first_drv_open,.write = first_drv_write,};//驱动入口static int first_drv_init(void){int ret;devno = MKDEV(major,minor); //创建设备号ret = register_chrdev( major, module_name, &first_drv_fops); //注册驱动告诉内核把这驱动加入到内核的链表中first_drv_class = class_create( THIS_MODULE, "first_driver_demo" ); //让代码在/dev下自动生成设备 (创建一个类)first_drv_class_dev = device_create( first_drv_class, NULL, devno, NULL, module_name); //创建设备文件(在类下面生成一个设备)return 0;}static void first_drv_exit(void){ printk("first_drv_exitn");device_destroy(first_drv_class,devno);class_destroy(first_drv_class);unregister_chrdev( major, module_name);}//内核将通过这个宏,来直到这个驱动的入口和出口函数module_init(first_drv_init);module_exit(first_drv_exit);MODULE_AUTHOR("zhy <1521772422@qq.com>");MODULE_LICENSE("GPL"); 4 Linux字符驱动编译

我使用了友善之臂2416的开发板,参考了提供的用户手册中的驱动模块编译方式,具体步骤如下:
1 在linux-3.6/drivers/char/kconfig 文件中config MINI2451_ADC后面添加first_driver的config:

config MINI2451_ADCtristate "ADC driver for FriendlyARM Mini2451 development boards"depends on MACH_MINI2451default yhelp this is ADC driver for FriendlyARM Mini2451 development boardsconfig MINI2451_FIRST_DRIVERtristate "My first driver for FriendlyARM Mini2451 development boards"depends on MACH_MINI2451default yhelp this is demo driver for FriendlyARM Mini2451 development boards

2 在linux3.6目录执行:

cp mini2451_linux_config .configmake menuconfig

出现kernal配置界面。
3 进入Device Drivers -》Character devices ,菜单中就可以看到刚才所添加的选项了。按下空格键将会选择为" M “,此意为要把该选项编译为模块方式;再按下空格会变为” * “,意为要把该选项编译到内核中,在此我们选择” M "即可,如图:

4 在linux-3.6/drivers/char/Makefile最末尾添加first_driver的编译选项。

obj-$(CONFIG_MINI2451_BUTTONS)+= mini2451_buttons.oobj-$(CONFIG_MINI2451_BUZZER)+= mini2451_pwm.oobj-$(CONFIG_MINI2451_ADC)+= mini2451_adc.oobj-$(CONFIG_MINI2451_FIRST_DRIVER) += first_driver.o

5 在linux3.6目录执行:

make modules

就可以在linux-3.6/drivers/char目录生成所需要的内核驱动文件first_driver.ko了。将编译出的first_driver.ko拷贝到开发板/lib/modules/3.6.0-FriendlyARM 目录。

cp /sdcard/first_driver.ko /lib/modules/3.6.0-FriendlyARM/cd lib/modules/3.6.0-FriendlyARM/

然后在板子中使用insmod命令加载写好的驱动程序:

insmod first_driver.ko

执行后没有任何打印出来,按理加载模块时会调用first_drv_init函数,会有printk打印出来,但是控制台没有任何消息。
使用lsmod命令查看发现驱动已经被加载:

lsmod

在/dev/目录下也能发现first_driver这个设备

网上查找博客发现应该时printk log等级设置的原因,使用demsg查看内核日志:

demsg

first_drv_init函数调用时的log果然有被打印,所以是printk log等级设置过低的原因,先查看一下当前打印等级。

cat /proc/sys/kernel/printk

显示出的4个数据分别对应控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别。
使用下面的命令设置当前日志级别:

echo 8 > /proc/sys/kernel/printk

这样所有级别 < 8的消息都可以显示在控制台上,但是一顿操作后仍然打印不出来,原因没有查明,所以还是老老实实的使用dmesg命令查看。
如果想卸载模块,执行以下命令:

rmmod first_driver

注意 : 要使模块能够正常卸载 , 必须把模块放入开发板/lib/modules/3.6.0-FriendlyARM 目录。

5 Linux字符驱动测试

驱动程序写完了,编写测试程序调用一下驱动,以下是测试程序:

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>int main(int argc, char **argv){ int fd; //声明设备描述符 int val = 1; //随便定义变量传入到 //根据设备描述符打开设备,之前定义的为first_driver fd = open("/dev/first_driver", O_RDWR); if(fd < 0) //打开失败 printf("can't openn"); write(fd, &val, 4); //根据文件描述符调用write return 0;}

编译测试程序:

arm-linux-gcc driver_test.c -o driver_test

将编译好的driver_test拷贝到开发板中运行,。

/sdcard/driver_test

使用dmesg 命令查看log

可以看到first_drv_open和first_drv_write函数有被调用,证明编写的驱动程序ok。

参考文章

1 驱动程序
2 Linux驱动主要类型简介
3 Linux驱动分类简介
4 详细到吐血 —— 树莓派驱动开发入门:从读懂框架到自己写驱动
5 linux内核打印数据到串口控制台,printk数据不打印问题

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。