第10章嵌入式Linux系统驱动程序设计.pptx
《第10章嵌入式Linux系统驱动程序设计.pptx》由会员分享,可在线阅读,更多相关《第10章嵌入式Linux系统驱动程序设计.pptx(67页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、嵌入式系统原理与开发教程嵌入式Linux系统驱动程序设计主讲人:赖树明东莞理工学院01Linux设备驱动基础02Linux内核模块编程 03Linux杂项设备驱动模型04Linux用户空间和内核空间数据交换05Linux GPIO API函数06Linux GPIO LED驱动07Linux GPIO 按键驱动0101Linux设备驱动基础Linux系统空间划分Linux 系统调用接口Linux系统文件描述符Linux内核系统框架 字符设备文件操作方法结构Linux系统设备分类Linux设备驱动基础Linux系统空间划分01用户空间和内核空间空间比例:03G:用户空间-运行应用程序-不能接到硬
2、件,3G4G:内核空间 运行驱动程序直接接触硬件?为什么要划分空间 -最主要的原因是考虑安全性。现代计算机都有两种以上的运行模式,不同的运行模式权限不一样,采取不同的执行模式的目的是为了保护操作系统。Linux系统空间划分用户进程运行在较低的特权级上,他们将不可能意外或故意地破坏其他进程或系统内核。因为每一个进程在系统上运行是都拥有自己的私有地址空间和数据。因此,用户进程造成的破坏会被局部化而不会影响到内核或其他进程。当用户进程需要完成在特权模式下才能完成的某些工作的时候,通过系统调用接口进入特权模式,然后执行调用所提供的有限功能。应用程序正常情况下都是运行在普通模式下,这部分代码运行的空间为
3、称用户空间,当代码通过系统调用接口进入到特权级运行时候,对应的代码执行空间称为内核空间。文件描述符是一个非负的整数,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理。名称 文件描述符 宏定义 标准输入0 STDIN_FILENO 标准输出1 STDOUT_FILENO 标准出错2 STDERR_FILENO文件描述符Linux设备驱动基础Linux 系统调用接口01获得文件描述符操作:open函数是用于打开
4、或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。所需头文件:#include#include#include 原型:int open(const char*pathname,int flags,int perms)。open函数头文件:#include#include#include 原型1:int open(const char*pathname,int flags);原型2:int open(const char*pathname,int flags,mode_t mode);功能:打开一个现有文件或创建一个新文件 参数:pathname:带路径的文件名,表示要操作
5、哪个文件。flags:文件打开方式,常用值:O_RDONLY:只读模式 O_WRONLY:只写模式 O_RDWR:可读可写 O_CREAT:如果文件不存在,则创建;mode:当创建新文件时设置文件访问权限的初始值,和用户掩码umask有关;返回值:=0:打开成功,值为文件描述符,这个数值关联pathname表示的文件;-1:打开失败。mode 可取值:S_IXOTH:其他用户有执行权0o001S_IWOTH:其他用户有写权限0o002S_IROTH:其他用户有读权限0o004S_IRWXO:其他用户有全部权限(权限掩码)0o007S_IXGRP:组用户有执行权限0o010S_IWGRP:组用户
6、有写权限0o020S_IRGRP:组用户有读权限0o040S_IRWXG:组用户有全部权限(权限掩码)0o070S_IXUSR:拥有者具有执行权限0o100S_IWUSR:拥有者具有写权限0o200S_IRUSR:拥有者具有读权限0o400S_IRWXU:拥有者有全部权限(权限掩码)0o700Linux设备驱动基础Linux 系统调用接口01read函数头文件:#include 原 型:ssize_t read(int fd,void*buf,size_t count);功 能:从fd关联的文件中读取最多count字节数据保存到buf指向的内存首地址;参 数:fd:要读取得文件描述符;buf:
7、数据指针,指向保存读取到的数据的内存地址;count:要读取的字节数量;返回值:出错:-1;0:读文件结束;0:成功读取到的字节数;0=返回值 count 时表示已经把文件数据读取完成;=count 并不能判断已经读取完成,即使文件只count字节。注意:每读取成功1字节,文件读写位置指针增加1。write函数头文件:#include 原 型ssize_t write(int fd,const void*buf,size_t count);功 能:把buf指向的内存开始的数据,写入到fd关联的文件中,最多写入ount字节;参 数:fd:要读取得文件描述符;buf:数据指针,指向要写入的数据的内
8、存首地址;count:要写入的的字节数量;返回值:-1:出错:0:成功写入到的字节数;注意:每写入成功1字节,文件读写位置指针增加1注意1:文件读写位置是共用一个读写位置指针的,每读写成功1字节,则文件读写位置相应增加1字节;注意2:读写函数调用都是从当前文件读写位置开始读写数据的;Linux设备驱动基础Linux 系统调用接口01lseek函数头文件:#include#include 原 型:off_t lseek(int fd,off_t offset,int whence);参 数:fd:之前用open()获得的一个文件描述符;offset:要调整的读写偏移量,可正可负,如何由whenc
9、e来确定的;whence参数分为下列三种:SEEK_SET:最终的读写位置=offset。SEEK_CUR:最终的读写位置=当前读写位置+offsetSEEK_END:最终的读写位置=文件末尾+offset返回值:=0:调用成功,值含义是距离文件开头字节偏移。-1:调用失败,错误代码保存在全局变量errno中,常见的erron的错误代码:EBADF:fildes不是一个打开的文件描述符。ESPIPE:文件描述符被分配到一个管道、套接字或FIFO。EINVAL:whence 取值不当。close函数头文件:#include 原 型:int close(int fd);参 数:fd:之前用open
10、()获得的一个文件描述符;返回值:成功0,否则返回-1,失败原因会被记录在errno中。常见的错误原因有:EBADF:fd不是有效的文件描述符EINTR:close()被某个信号处理程序中断EIO:关闭文件时发生了IO错误Linux设备驱动基础Linux 系统调用接口01一切皆是文件Linux设备驱动基础Linux系统设备分类01Linux系统中一切皆是文件,在Linux系统中有三类设备,分别是字符设备、块设备、网络设备。n字符设备:以字节为单位,进行顺序读写的硬件设备,对这种设备的读写是实时的,在/dev/目录下会有一个设备文件与之相对应。属于字符设备的硬件非常多,如鼠标、键盘、LED、按键
11、、定时器、UART等都属于字符设备,实际上我们看到的大部分硬件都是属于字符设备,字符设备驱动是学习的重点;n块设备:以块为单位,可以进行随机读写的硬件设备,对这类设备的读写一般都是带有缓冲区功能的,在/dev/目录下会有一个设备文件与之相对应。属于块设备的硬件一般都是存储类设备,如硬盘、U盘、Flash、SD卡、eMMC等大容量存储设备;n网络设备:面向网络数据的接收和发送而设计的设备,一般是针对网络通信类设备,如以太网控制器、WiFi无线网卡、NFC、CanBus、蓝牙等。网络设备在/dev/目录下并不存在与之相对应的设备文件,而是由系统分配给它们唯一的名字,如eth0、ens33等,通过i
12、fconfig命令可以查看。Linux系统设备文件Linux设备驱动基础Linux系统设备分类01当字符设备或块设备类型的硬件驱动程序安装到linux内核时,就会/dev目录下生成设备设备文件节点,使用ls-/dev/设备名 命令形式可以查看到设备的详细信息,如查看系统中的串口设备:#ls-l/dev/ttyS*crw-rw-1 root dialout 4,64 Apr 10 16:23/dev/ttyS0crw-rw-1 root dialout 4,65 Jan 28 2018/dev/ttyS1.crw-rw-1 root dialout 4,68 Jan 28 2018/dev/tt
13、yS4输出显示串列表中 4 表示是设备的主设备号,6468表示设备的次设备号;这5个串口是共用一份驱动程序的,驱动程序中是通过次设备号来驱动用户访问的是哪一个物理串口。由此我们知道设备文件是有两个普通文件不具备的特征:主设备号和次设备号。主设备号:Linux内核使用主设备号来表示同一种设备,比如上面的串口,不管多少个其主设备号都相同。次设备号:Linux内核使用主设备号来表示同一种设备中的具体哪一个,比如上面5个串口设备,使用6468分别标识串口04。内核就是通过主设备号和次设备号来共同匹配驱动程序,找到正确的硬件设备来操作的。命令创建设备文件:mknod /dev/c 文件是Linux系统中
14、的重要概念,它不仅仅是对普通文件的操作接口,也是设备通信、进程间通信、网络通信的重要编程接口。硬件设备在linux系统中也体现为一个文件,这类不是普通文件,而是设备文件。创建文件创建只能使用,示例:做rootfs时创建了一个控制台设备 mknod dev/console c 5 1一个led,按键,鼠标,键盘,硬盘,声卡,显卡,UART,WDT,EEPROM.这些在内核都是文件。不同的文件打开/读/写/关闭方法不一样,对于硬件设备也是一样,实现硬件驱动关键是实现它们的文件方法。操作文件方法:打开文件,操作文件,关闭文件。推论:硬件的操作方法:打开硬件对应的设备文件,读取写控制操作设备文件,关闭
15、设备文件。一切都是文件Linux设备驱动基础Linux系统设备分类01编写Linux驱动程序,难点不是在于对硬件的具体操作,而是需要了解清楚驱动程序的框架,并在这个框架中找到修改的位置,加入与设计相关的硬件控制代码。在用户进程通过系统调用而间接调用驱动程序时,系统进入核心态,这时不再是抢先式调度。即系统必须在你的驱动程序的子函数返回后才能进行其他的工作。应用程序通过调用某些C库函数或标准系统调用接口进入内核空间调用硬件驱动程序。常 用 接 口 如 open(),read(),write(),ioctl(),close()等等。Linux内核系统框架 02 编写字符设备驱动80%工作都是实现设备
16、的文件操作方法,内核使用 这个结构来描述设备支持的方法;Linux字符设备文件操作方法03 struct file_operations struct module*owner;loff_t (*llseek)(struct file*,loff_t,int);/对应 lseek 系统调用函数 ssize_t(*read)(struct file*,char _user*,size_t,loff_t*);/对应 read 系统调用函数 ssize_t(*write)(struct file*,const char _user*,size_t,loff_t*);/对应 write 系统调用函数
17、.unsigned int(*poll)(struct file*,struct poll_table_struct*);/对应 poll,select 系统调用函数 long(*unlocked_ioctl)(struct file*,unsigned int,unsigned long);/对应 ioctl 系统调用函数 int(*open)(struct inode*,struct file*);/对应 open 系统调用函数 .int(*release)(struct inode*,struct file*);/对应 close 系统调用函数 .;说明:对于具体设备,不需要实现全部成员
18、,如:实现led驱动:控制亮灭,查询亮灭状态。控制亮灭:实现write接口查询亮灭状态:实现read接口初始化GPIO引脚:实现open接口,也可以不实现,可以把初始化代码写在模块的初始化函数中,这样更好,这样可以保证GPIO引脚初始化只会执行一次。如:实现按键驱动:只需要实现read接口,返回状态即可如:实现AT24C02驱动:标准write,read,llseek 接口,这样就可以实现随机读/写。Linux字符设备文件操作方法03 常用成员说明:open :打开设备,对应于open系统调用,如果设备不需要初始化工作,可以不实现内核会默认实现,永远返回成功状态。release:关闭设备,和o
19、pen接口功能完全相反,如果在open接口中申请资源,必须这里进行释放。这个接口也可以不实现,应用也可以调用,内核会使用默认函数。llseek:移动文件读写位置指针,对应于lseek系统调用。read :从设备中读取数据,对应于read系统调用write:向设备写入数据,对应于write系统调用poll:查询设备状态(可读/可写/异常),对应于poll/select系统调用unlocked_ioctl:设置/查询设备的工作参数,对设备进行控制,对应于ioctl系统调用0202Linux内核模块开发Linux内核模块介绍Linux内核模块编程Linux内核常用命令Linux设备驱动程序都是以模块
20、的形式发布,极大地提高了设备使用的灵活性:用户只需要拿到相关驱动模块,再安装linux内核中,即可灵活地使用你的设备。使用Linux模块的优点 1.用户可以随时扩展Linux系统的功能。2.当要修改Linux系统的驱动时,只需要卸载旧模块,重新安安装新编译的模块即可。3.系统中如果需要使用新模块,不必重新编译内核,只要插入相应的模块即可。4.减小Linux内核的体积,节省flash。嵌入式Linux内核模块开发02Linux内核模块介绍Linux内核模块介绍Linux内核模块必须的头文件Linux内核模块初始化函数Linux内核驱动卸载函数Linux内核模块许可证声明用户自定的功能函数实现Li
21、nux内核模块组成嵌入式Linux内核模块开发02Linux内核模块编程Linux内核模块必须的头文件#include#include Linux内核模块初始函数函数 1)当内核模块被加载时,初始化函数会被内核执行,完成本模块的相关初始化工作。2)使用 _init 修饰,同时使用 module_init()修饰Linux内核驱动卸载函数 1)当内核模块被卸载时,卸载函数会被内核执行,完成本模块的资源释放工作,功能与模块初始化函数完全相反。2)使用 _exit 修饰,同时使用module_exit()修饰。Linux内核模块许可证声明 MODULE_LICENSE():用来声明本代码发布的协议类
22、型。MODULE_AUTHOR():用来声明作者;MODULE_DESCRIPTION():用来声明模块的描述(模块功能说明)#include /必须的头文件#include /必须的头文件static int _init hello_init(void)/模块初始化函数printk(Hello world,priority=DEFAULT_MESSAGE_LOGLEVELn);return 0;static void _exit hello_exit(void)/模块初始化函数printk(Goodbye,cruel world!,priority=DEFAULT_MESSAGE_LOGLE
23、VELn);module_init(hello_init);/使用module_init 声明hello_init 为初始化函数module_exit(hello_exit);/使用module_init 声明hello_init 为初始化函数MODULE_LICENSE(“Dual BSD/GPL”);/使用MODULE_LICENSE软件发布许可协议声明MODULE_AUTHOR(“BENSON”);/使用MODULE_AUTHOR声明作者MODULE_DESCRIPTION(“STUDY_MODULE”);/使用MODULE_DESCRIPTION声明模块介绍 Linux内核模块代码模板
24、嵌入式Linux内核模块开发02Linux内核模块编程#变量 obj-m,这个是变量名固定,用来指定编译的模块#hello.o 就是 hello.c 对应的.o 文件obj-m:=hello.o#X86内核源码路径:如要在X86测试编译模块则使用以下路径#KDIR :=/lib/modules/$(shell uname-r)/build#如果要编译ARM的模块,则修改成ARM的内核源码路径#修改为自己的内核源码路径,且内核源码成功编译过没有清除KDIR :=/home/edu118/work/source/kernel-rockchipall:make -C$(KDIR)M=$(PWD)mo
25、dules clean:make -C$(KDIR)M=$(PWD)modules cleanLinux系统Makefile使用02编译Linux内核模块MakefileMakefile编程进阶p这个Makefile 模板是通用的p根据自己的模块代码c文件修改obj-m值p根据自己的Linux内核源码路径修改KDIR值pall 目标用来编译内核模块代码pclean 目标用来清除编译的生成的文件p编译:make 编译完成会生成.ko 文件p清除编译信息:使用 make clean 0303Linux杂项设备驱动模型杂项设备驱动基础杂项设备驱动模型代码模板杂项设备驱动模型代码模板杂项设备驱动模型特
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第10章 嵌入式Linux系统驱动程序设计 10 嵌入式 Linux 系统 驱动程序 设计
限制150内