linux驱动程序结构框架及工作原理分别是什么?

linux驱动程序结构框架及工作原理分别是什么?,第1张

一、Linux device driver 的概念\x0d\x0a\x0d\x0a系统调用是 *** 作系统内核和应用程序之间的接口,设备驱动程序是 *** 作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象 *** 作普通文件一样对硬件设备进行 *** 作。设备驱动程序是内核的一部分,它完成以下的功能:\x0d\x0a\x0d\x0a1、对设备初始化和释放;\x0d\x0a\x0d\x0a2、把数据从内核传送到硬件和从硬件读取数据;\x0d\x0a\x0d\x0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;\x0d\x0a\x0d\x0a4、检测和处理设备出现的错误。\x0d\x0a\x0d\x0a在Linux *** 作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O *** 作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。\x0d\x0a\x0d\x0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。\x0d\x0a\x0d\x0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。\x0d\x0a\x0d\x0a二、实例剖析\x0d\x0a\x0d\x0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。\x0d\x0a\x0d\x0a由于用户进程是通过设备文件同硬件打交道,对设备文件的 *** 作方式不外乎就是一些系统调用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:\x0d\x0a\x0d\x0aSTruct file_operatiONs {\x0d\x0a\x0d\x0aint (*seek) (struct inode * ,struct file *, off_t ,int)\x0d\x0a\x0d\x0aint (*read) (struct inode * ,struct file *, char ,int)\x0d\x0a\x0d\x0aint (*write) (struct inode * ,struct file *, off_t ,int)\x0d\x0a\x0d\x0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int)\x0d\x0a\x0d\x0aint (*select) (struct inode * ,struct file *, int ,select_table *)\x0d\x0a\x0d\x0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long)\x0d\x0a\x0d\x0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *)\x0d\x0a\x0d\x0aint (*open) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*release) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*fsync) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*fasync) (struct inode * ,struct file *,int)\x0d\x0a\x0d\x0aint (*check_media_change) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*revalidate) (dev_t dev)\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write *** 作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。\x0d\x0a\x0d\x0a下面就开始写子程序。\x0d\x0a\x0d\x0a#include 基本的类型定义\x0d\x0a\x0d\x0a#include 文件系统使用相关的头文件\x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0aunsigned int test_major = 0\x0d\x0a\x0d\x0astatic int read_test(struct inode *inode,struct file *file,char *buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint left用户空间和内核空间\x0d\x0a\x0d\x0aif (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )\x0d\x0a\x0d\x0areturn -EFAULT\x0d\x0a\x0d\x0afor(left = count left >0 left--)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0a__put_user(1,buf,1)\x0d\x0a\x0d\x0abuf++\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0areturn count\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。\x0d\x0a\x0d\x0astatic int write_test(struct inode *inode,struct file *file,const char *buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0areturn count\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic int open_test(struct inode *inode,struct file *file )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_INC_USE_COUNT模块计数加以,表示当前内核有个设备加载内核当中去\x0d\x0a\x0d\x0areturn 0\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic void release_test(struct inode *inode,struct file *file )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_DEC_USE_COUNT\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这几个函数都是空 *** 作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。\x0d\x0a\x0d\x0astruct file_operations test_fops = {?\x0d\x0a\x0d\x0aread_test,\x0d\x0a\x0d\x0awrite_test,\x0d\x0a\x0d\x0aopen_test,\x0d\x0a\x0d\x0arelease_test,\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。\x0d\x0a\x0d\x0aint init_module(void)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint result\x0d\x0a\x0d\x0aresult = register_chrdev(0, "test", &test_fops)对设备 *** 作的整个接口\x0d\x0a\x0d\x0aif (result \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0amain()\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint testdev\x0d\x0a\x0d\x0aint i\x0d\x0a\x0d\x0achar buf[10]\x0d\x0a\x0d\x0atestdev = open("/dev/test",O_RDWR)\x0d\x0a\x0d\x0aif ( testdev == -1 )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aprintf("Cann't open file \n")\x0d\x0a\x0d\x0aexit(0)\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0aread(testdev,buf,10)\x0d\x0a\x0d\x0afor (i = 0i 回答于 2022-11-18

需要一定的努力才可以学好:

Linux设备驱动是linux内核的一部分,是用来屏蔽硬件细节,为上层提供标准接口的一种技术手段。为了能够编写出质量比较高的驱动程序,要求工程师必须具备以下几个方面的知识:

1、 熟悉处理器的性能

如:处理器的体系结构、汇编语言、工作模式、异常处理等。对于初学者来说,在还不熟悉驱动编写方法的情况下,可以先不把重心放在这一项上,因为可能因为它的枯燥、抽象而影响到你对设备驱动的兴趣。随着你不断地熟悉驱动的编写,你会很自然的意识到此项的重要性。

2、掌握驱动目标的硬件工作原理及通讯协议

如:串口控制器、显卡控制器、硬件编解码、存储卡控制器、I2C通讯、SPI通讯、USB通讯、SDIO通讯、I2S通讯、PCI通讯等。编写设备驱动的前提就是需要了解设备的 *** 作方法,所以这些内容的重要程度不言而喻。但不是说要把所有设备的 *** 作方法都熟悉了以后才可以写驱动,你只需要了解你要驱动的硬件就可以了。

一、掌握硬件的控制方法

如:中断、轮询、DMA 等,通常一个硬件控制器会有多种控制方法,你需要根据系统性能的需要合理的选择 *** 作方法。初学阶段以实现功能为目的,掌握的顺序应该是,轮询->中断->DMA。随着学习的深入,需要综合考虑系统的性能需求,采取合适的方法。

二、良好的GNU C语言编程基础

如:C语言的指针、结构体、内存 *** 作、链表、队列、栈、C和汇编混合编程等。这些编程语法是编写设备驱动的基础,无论对于初学者还是有经验者都非常重要。

三、 良好的linux *** 作系统概念

如:多进程、多线程、进程调度、进程抢占、进程上下文、虚拟内存、原子 *** 作、阻塞、睡眠、同步等概念及它们之间的关系。这些概念及方法在设备驱动里的使用是linux设备驱动区别单片机编程的最大特点,只有理解了它们才会编写出高质量的驱动。

四、掌握linux内核中设备驱动的编写接口

如:字符设备的cdev、块设备的gendisk、网络设备的net_device,以及基于这些基本接口的framebuffer设备的fb_info、mtd设备的mtd_info、tty设备的tty_driver、usb设备的usb_driver、mmc设备的mmc_host等。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/8489653.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-17
下一篇2023-04-17

发表评论

登录后才能评论

评论列表(0条)

    保存