第 8 章 嵌入式 linux 驱动程序设计

51
第 8 第 第第第 Linux 第第第第第第

Upload: jiro

Post on 14-Jan-2016

144 views

Category:

Documents


0 download

DESCRIPTION

第 8 章 嵌入式 Linux 驱动程序设计. 本章目标. 了解 Linux 设备驱动程序的基础知识; 掌握 Linux 驱动模块的构造和装载方法; 掌握字符设备驱动程序的基本结构和开发方法; 掌握用户空间调用设备驱动程序的方法. 本章结构. 设备驱动程序简介. 字符设备. 设备的分类和特点. 块设备. Linux 驱动程序概述. 网络设备. 设备驱动的 Hello World 模块. 构造和运行模块. 内核驱动模块和应用程序对比. 编译和装载驱动模块. 字符设备驱动. 8.1 设备驱动程序简介. 驱动程序的特点 - PowerPoint PPT Presentation

TRANSCRIPT

第 8 章 嵌入式 Linux 驱动程序设计

本章目标

了解 Linux 设备驱动程序的基础知识; 掌握 Linux 驱动模块的构造和装载方法; 掌握字符设备驱动程序的基本结构和开发方法; 掌握用户空间调用设备驱动程序的方法 .

本章结构

设备驱动程序简介 设备驱动程序简介

Linux 驱动程序概述 Linux 驱动程序概述

设备驱动的 Hello World 模块设备驱动的 Hello World 模块

内核驱动模块和应用程序对比 内核驱动模块和应用程序对比

编译和装载驱动模块 编译和装载驱动模块

构造和运行模块 构造和运行模块

设备的分类和特点 设备的分类和特点

字符设备字符设备

网络设备网络设备

块设备块设备

字符设备驱动 字符设备驱动

8.1 设备驱动程序简介

驱动程序的特点 驱动程序直接操控硬件,是应用和硬件设备之间的一个软件层 这个软件层一般在内核中实现,但也可以在用户空间实现,也就是所说的内核态

驱动和用户态驱动。我们主要讨论内核态驱动。 设备驱动程序的作用在于提供机制,而不是提供策略,编写驱动程序时不要给用

户强加任何策略• 机制:驱动程序能实现什么功能• 策略:用户如何使用这些功能 通过驱动程序,应用软件可以安全高效的访问硬件 驱动程序作为一个隔离的中间层软件,将底层细节隐藏起来,提高了软件的可移

植性

8.2 设备的分类和特点 (1)

设备分类

字符设备 (char device) 块设备 (block device) 网络设备 (network device)

8.2 设备的分类和特点 (2)

各类设备的特点

字符设备特点

字符设备是个能够像字节流一样来存取的设备 ( 如同一个文件 )

字符设备通过 /dev 下的文件系统结点来访问。字符设备驱动通常至少需要实现 open, close, read, 和 write 等系统调用

字符设备和普通文件的不同之处在于,大部分字符设备仅仅是只能顺序访问的数据通道,不能前后移动访问指针。然而,也存在和数据区或者文件特性类似的字符设备,访问它们时也可前后移动访问指针。比如 framebuffer 设备就是这样的设备,应用程序可以使用 mmap 或 lseek 访问图像的各个区域

8.2 设备的分类和特点 (3)

块设备特点

块设备通过位于 /dev 目录的文件系统结点来存取

字符设备通过文件系统结点来访问。块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同

块设备有专门的接口,块设备的接口必须支持挂装( mount )文件系统。应用程序一般通过文件系统来访问块设备上的内容

8.2 设备的分类和特点 (4)

网络设备特点

网络设备驱动不同于字符设备和块设备驱动,不是通过 /dev 下的文件节点来访问,而是通过单独的网络接口来访问

任何一个网络事务都通过一个网络接口,即一个能够和其他主机交换数据的设备。通常该接口代表一个硬件设备(如网卡),但也可能是一个纯粹的软件设备,比如环回接口( loopback )

内核与网络驱动程序间的通讯完全不同于内核和字符设备以及块设备驱动程序之间的通信,内核调用一套和数据包传输相关的函数

8.3 构造和运行模块

8.3.1 、设备驱动的 Hello World 模块8.3.2 、内核驱动模块与应用程序对比8.3.3 、编译和装载驱动模块

驱动程序加入内核的方法 把所有需要的功能都编译到内核中

导致的问题:

1) 生成的内核镜像( Image )文件会很大

2) 如果我们要在现有的内核中新增或删除功能,将不得不重新编译和装载内核。

有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码可被动态地加载到内核中呢

Linux 提供了这样的一种机制可以实现以上效果,这种机制被称为 :

模块(Module )

1) Linux 内核提供了对许多模块类型的支持 , 包括但不限于 , 设备驱动

2)每个模块由目标代码组成 ( 没有连接成一个完整可执行程序 ), 我们可以使用 insmod 工具将模块动态加载到正在运行内核中,也可以及通过 rmmod 程序移除模块

8.3.1 设备驱动的 Hello World 模块 (1)

#include <linux/init.h>#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static int __init hello_init(void){

printk(KERN_ALERT "Hello world\n"); return 0;

}

static void __exit hello_exit(void){

printk(KERN_ALERT " Hello world exit\n");}module_init(hello_init);module_exit(hello_exit);

自由许可证

模块卸载宏

用法类似于 printf ,但它有优先级 ( 比如

KERN_ALERT)

模块初始化宏

宏,告诉内核这两个函数只会在加载和卸

载模块时使用

8.3.1 设备驱动的 Hello World 模块 (2) 编译内核模块 #gcc –DMODULE –D__KERNEL__ -c hello.c –I/your_kernel_path/linux/include会生成 hello.ko

加载内核模块: insmod <模块名称 .ko> #insmod ./hello.ko Hello world

查看内核中已装载的模块 #lsmod | grep hello

卸载内核模块: rmmod <模块名称 > #rmmod hello Hello world exit

8.3.1 设备驱动的 Hello World 模块 (3) Linux 内核模块的程序结构 module_init()--- 模块加载函数(必须) 当通过 insmod 或 modprobe命令加载内核模块时,模块的加载函数会自动被内核 执行,完成模块的相关初始化工作 module_exit()--- 模块卸载函数(必须) 当通过 rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块装

载函数相反的功能 MODULE_LICENSE()--- 模块许可证声明(必须)

模块许可证( LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE, 模块被加载时,将收到内核被污染( kernel tainted )的警告

module_param()--- 模块参数(可选) 模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。 EXPORT_SYMBOL()--- 模块导出符号(可选) 内核模块可以导出符号( symbol ,对应于函数或变量),这样其他模块可以使用本模

块中的变量或函数 其他一些声明 MODULE_XXXXX()--- 模块声明(可选)

8.3.1 设备驱动的 Hello World 模块 (4) 模块加载函数static int __init initialization_function(void){

/* 初始化代码 */}module_init(initialization_function);

初始化函数应当声明成静态的( static ) , 因为它们不会在特定文件之外可见 __init 标志表明该函数只是在初始化时使用。模块加载器在模块加载后会丢掉这个初始化函数 , 这样可将该函数占用的内存释放出来,以作他用。 原型: #define __init __attribute__ ((__section__(“.init.text”)))

moudle_init 的使用是强制性的 , 这个宏定义会在模块目标代码中增加一个特殊的段 , 用于说明内核模块初始化函数所在的位置。没有这个定义 , 初始化函数不会被调用。

8.3.1 设备驱动的 Hello World 模块 (5) 模块卸载函数 static void __exit cleanup_function(void) { /* 释放资源 */ } module_exit(cleanup_function);

该函数在模块被移除前注销接口并释放所有所占用的系统资源 清理函数没有返回值 , 因此它被声明为 void __exit 修饰符标识这个代码是只用于模块卸载 ( 通过使编译器把它放在特殊的 ELF 段 )原型: #define __exit __attribute__ ((__section__(“.exit.text”)))

moudle_exit 声明对于使得内核能够找到模块的清理函数是必要的

8.3.1 设备驱动的 Hello World 模块 模块参数为了增加驱动程序的灵活性,内核允许对驱动程序指定参数,而这些参数可在加载驱动程序模块时改变。如下面实例:#include <linux/init.h>#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");static char *whom = "world";static int howmany = 1;static int hello_init(void){

int i; for(i=0;i<howmany;i++) {

printk(KERN_ALERT "Hello %s\n",whom); } return 0;}static void hello_exit(void){

printk(KERN_ALERT " Hello world exit\n");}module_init(hello_init);module_exit(hello_exit);module_param(howmany, int, S_IRUGO);module_param(whom, charp, S_IRUGO);

module_param (参数名,

参数类型,参数读 / 写权限)

8.3.1 设备驱动的 Hello World 模块module_param (参数名,参数类型,参数读 / 写权限) static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);

内核支持的模块参数类型包括: byte 、 short 、 ushort 、 int 、 uint 、 long、 ulong、 charp( 字符指针 ) 、 bool 或 i

nvbool (布尔的反),以‘ u’开头的为无符号值。

模块也可以拥有参数数组形式为“module_param_array(数组名,数组类型,数组长,参数读 / 写权限)”。运行 insmod 或 modprobe命令时,应使用逗号分隔输入的数组元素。

装载模块时改变参数:可通过 insmod 或 modprobe insmod hello_ext.ko howmany=5 whom="Students" modprobe 也可以从它的配置文件 (/etc/modprobe.conf)读取参数的值

8.3.1 设备驱动的 Hello World 模块 模块导出符号

Linux 内核头文件提供了一个方便的方法用来管理符号的对模块外部的可见性 , 因此减少了命名空间的污染 (命名空间的名称可能会与内核其他地方定义的名称冲突 ), 并且适当信息隐藏。如果模块需要输出符号给其他模块使用 , 应当使用下面的宏定义 :

EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);

注:1 ) _GPL 版本的宏定义的导出符号只能对 GPL 许可的模块可用2)符号必须在模块文件的全局部分导出 , 不能在函数中导出

8.3.1 设备驱动的 Hello World 模块 (6)

模块声明与描述

MODULE_AUTHOR(author); --- 声明模块的作者 MODULE_DESCRIPTION(description); --- 声明模块的描述

MODULE_VERSION(version_string); --- 声明模块的版本 MODULE_DEVICE_TABLE(table_info); --- 声明模块的设

备表MODULE_ALIAS(alternate_name); --- 声明模块的别名

8.3.1 设备驱动的 Hello World 模块 (7) 模块的使用计数Linux2.4 内核中模块自身通过MOD_INC_USE_COUNT(加一计数)和MOD_DEC_USE_COUNT(减一计数)宏来管理自己被使用的计数

Linux2.6 内核中提供了模块计数管理接口int try_module_get(struct module *module);和void module_put(struct module *module);取代 Linux2.4内核中的模块使用计数管理宏

注:在 Linux2.6内核下,对于设备驱动工程师而言,很少需要亲自调用 try_module_get() 和module_put() ,因为模块的计数管理由内核里更底层的代码(如总线驱动或是此类设备共用的核心模块)来实现,从而简化了设备驱动的开发

8.3.2 内核驱动模块与应用程序对比

应用程序是一个进程 编程从主函数 main ()开始 主函数 main返回即是进程结束 驱动程序是一系列内核函数 驱动程序向内核添加了一些函数是内核的一部分 Open () Release () Read () Write () 这些函数由内核在适当的时候来调用 这些函数可以用来完成硬件访问等操作

8.3.3 编译和装载驱动模块 (1) 编译模块 编译模块的 makefile 样例: ifneq ($(KERNELRELEASE),) obj-m := hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif

对于本章所将的“ hello world”程序的 makefile ,下面一行就够了 : obj-m := hello.o

如果我们想由两个源文件 ( 比如 file1.c 和 file2.c ) 构造出一个名称为 module.ko 的模块 , 则正确的 makefile 可如下编写 :

obj-m := module.o module-objs := file1.o file2.o

8.3.3 编译和装载驱动模块 (2)

装载和卸载模块 装载模块

Insmod 和 modprobe 可以用来装载模块 , 其中 Insmod 和 modprobe 主要区别 : modprobe会考虑要装载的模块是否引用了一些当前内核不存在的符号。如果有这类引用, modprobe会在当前模块路径中搜索定义了这些符号的其他模块,并同时将这些

模块也装载到内核。如果在这种情况下使用 insmod ,该命令则会失败,并在系统日志文

件中记录“ unresolved symbols (未解析的符号)”消息。 卸载模块 从内核中卸载模块可以用 rmmod工具。 注意,如果内核认为该模块任然在使用状态,或者内核被禁止移除该模块,则无法移除该模块。

8.4 字符设备驱动程序基本结构

8.4.1 主要的概念和结构体8.4.2 Linux 字符设备驱动程序的组成8.4.3 添加驱动程序到内核中

8.4.1 主要的概念和结构体 (1)

什么是主设备号 / 次设备号 主设备号是内核识别一个设备的标识。它是一个整数,范围从 0到 4095,通常使用 1到 255

次设备号由内核使用,用于正确确定设备文件所指的设备。它是一个整数,范围从 0到 1048575,但一般使用 0 到 255

设备编号的内部表达 dev_t 类型 (32位):用来保存设备编号 ( 包括主设备号 (12位 ) 和次设备号 (20位 )) 从 dev_t获得主设备号和次设备号:

MAJOR(dev_t);MINOR(dev_t);

将主设备号和次设备号转换成 dev_t 类型:MKDEV(int major , int minor) ;

8.4.1 主要的概念和结构体 (2)

分配主设备号 手工分配主设备号:找一个内核没有使用的主设备号来使用。 动态分配主设备号:

int alloc_chrdev_resion(dev_t *dev , unsigned int firstminor ,

unsigned int count , char *name) ;

参数:

dev: 输出的设备号

firstminor :应该是要使用的被请求的第一个次设备号

count :所请求的连续设备编号的个数

name :和该编号范围关联的设备名称

8.4.1 主要的概念和结构体 (3)

cdev 结构体struct cdev { struct kobject kobj; /* 内嵌的 kobject 对象 */ struct module *owner; /*所属模块 */ struct file_operations *ops; /*文件操作结构体 */ struct list_head list; dev_t dev; /*设备号 */ unsigned int count; }; 操作 cdev 的函数

void cdev_init( struct cdev *, struc t file_operations *);struct cdev *cdev_alloc(void) ;int cdev_add(st ruct cdev *, dev_t, unsigned) ;void cdev_del(struct cdev *);

8.4.1 主要的概念和结构体 (4)

file_operations 结构体 字符驱动和内核的接口: file_operations 结构体(在 include/linux/fs.h定义 ) 。 字符驱动只要实现一个 file_operations 结构体,并注册到内核中,内核就有了操作此

设备的能力。 file_operations 的主要成员:

struct module *owner: 指向模块自身open :打开设备release :关闭设备read :从设备上读数据write :向设备上写数据ioctl : I/O控制函数llseek :定位读写指针mmap :映射设备空间到进程的地址空间

file 结构体 file 结构:和 file_operations 结构相关的一个结构体。描述 一个正在打开的设备文

件。 loff_t f_pos: 当前读 / 写位置 unsigned int f_flags: 标识文件打开时,是否可读或可写,如 0_RDONLY、 O_NONB

LOCK和 O_SYNC struct file_operations *f_op: 与文件相关的操作,指向所实现的 struct file_operatio

ns void *private_data: 私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略

这个字段。

8.4.1 主要的概念和结构体 (5)

8.4.1 主要的概念和结构体 (6)

inode 结构体 内核用 inode 结构在内部表示文件,因此它和 file 结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的 file 结构,但它们都指向单个 inode 结构。

Inode 结构中的两个主要字段:dev_t i_rdev ; 对表示设备文件的 inode 结构,该字段包含了真正的设备编号。struct cdev *i_cdev ; struct cdev 是表示字符设备的内核的内部结构。当 inode 指向一个字符设备文

件时,该字段包含了指向 struct cdev 结构的指针。从一个 inode 中获得主设备号和次设备号:unsigned int iminor(struct inode *inode) ;unsigned int imajor(struct inode *inode) ;

8.4.2 Linux 字符设备驱动程序的组成 (1) 注册和注销设备 向内核注册字符设备,在模块或者驱动初始化的时候调用。

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); (Linux2.4或以前版本)int register_chrdev_region(dev_t first , unsigned int count,

char *name) ;int alloc_chrdev_resion(dev_t *dev , unsigned int firstminor ,

unsigned int count , char *name) ; 注销设备驱动程序,在模块或者驱动销毁的时候调用 int unregister_chrdev(unsigned int major, const char

*name); (Linux2.4或以前版本) void unregister_chrdev_region(dev_t first ,

unsigned int count) ;

8.4.2 Linux 字符设备驱动程序的组成 (2) 字符设备驱动模块加载模板/ / 设备驱动模块加载函数static int __init xxx_init(void) {

...cdev_init(&xxx_dev.cdev, &xxx_fops); / /初始化 cdevxxx_dev.cdev.owner = THIS_MODULE;/ /获取字符设备号if (xxx_major){ register_chrdev_region(xxx_dev_no, 1, DEV_NAME);}else{

alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);}ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1 ) ; / /注册设备...}

8.4.2 Linux 字符设备驱动程序的组成 (3)

字符设备驱动模块卸载模板

/*设备驱动模块卸载函数 */

static void __exit xxx_exit(void)

{

unregister_chrdev_region(xxx_dev_no, 1); / /释放占用的设备号

cdev_del(&xxx_dev.cdev); / /注销设备

...

}

8.4.2 Linux 字符设备驱动程序的组成 (4) 打开和关闭 (1)

open

int open(struct inode *inode, struct file *filp) ;

模块使用计数加 1

识别次设备号,如有必要更新 f_op 指针。分配并填写置于 filp>private_data里的数据结构。

硬件操作:

检查设备相关错误(诸如设备未就绪或类似的硬件问题);

如果设备是首次打开,则对其初始化;

如果有中断操作,申请中断处理程序;

8.4.2 Linux 字符设备驱动程序的组成 (5)

打开和关闭( 2 ) release

int release(struct inode *inode, struct file *filp) ; 模块使用计数减 1 释放由 open 分配的,保存在 filp>private_data里的所有内容。 硬件操作:

如果申请了中断,则释放中断处理程序。

在最后一次关闭操作时关闭设备。

8.4.2 Linux 字符设备驱动程序的组成 (6) read/write ( 1 )

ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);

ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp); 参数说明

参数 filp 是文件指针

参数 buff 是指向用户空间的缓冲区,这个缓冲区或者保存将写入的数据,或者是一个存放新读入数据的空缓冲区。

参数 count 是请求传输的数据长度。

最后的 offp 是一个指向“ long offset type (长偏移量类型)”对象的指针,这个对象指明用户在文件中存取操作的位置。

8.4.2 Linux 字符设备驱动程序的组成 (7) read/write ( 2 ) 用户空间和内核空间之间的数据拷贝过程,不能简单的用指针操作或者 memcpy来进行

数据拷贝。原因: 用户空间的数据是可以被换出的,会产生一个页面失效异常。 用户空间的地址无法在内核空间中使用。 用户空间和内核空间之间进行数据拷贝的函数:

unsigned long copy_from_user(void *to, const void __user *from, unsigned long count) ; unsigned long copy_to_user(void __user *to, const void *from, unsigned long count ); 如果要复制的内存是简单类型,如 char 、 int 、 long 等,则可以使用简单的 put_user

() 和 get_user()

8.4.2 Linux 字符设备驱动程序的组成 (8)

read/write ( 3 ) 读设备模板ssize_t xxx_read(struct file *filp, char __user *buf, size_t count , loff_t*f_pos) { ...

copy_to_user(buf, ..., ... ); ... } 写设备模板 ssize_t xxx_write(struct file *fil p, const char __user *buf , size_t count ,

loff_t *f_pos) {

... copy_from_user(..., buf, ... ); ... }

8.4.2 Linux 字符设备驱动程序的组成 (9)

ioctl 函数( 1 )

为设备驱动程序执行“命令”提供了一个特有的入口点,用来设置或者读取设备的属性信息。

int ioctl(struct inode *inode, struct file *filp,

unsigned int cmd, unsigned long arg);

cmd :事先定义的 IO控制命令

arg : arg为对应于 cmd命令的参数

8.4.2 Linux 字符设备驱动程序的组成 (10) ioctl 函数( 2) cmd参数 不推荐用 0x1 , 0x2, 0x3之类的值, Linux 对 ioctl() 的 cmd参数有特殊的定义如下:

构造命令编号的宏:_IO(type , nr) 用于构造无参数的命令编号;_IOR(type , nr , datatype) 用于构造从驱动程序中读取数据的命令编号;_IOW(type , nr , datatype) 用于写入数据的命令;_IOWR(type , nr , datatype) 用于双向传输。 type 和 number 位字段通过参数传入,

而 size 位字段通过对 datatype参数取 sizeof获得。 样例:#define XXX_IOC_MAGIC ‘k’ /*使用“ k”作为幻数 *//* XXX_IOCXXXx 是定义的对应的 ioctl 命令 */#define XXX_IOCXXX1 _IO(XXX_IOC_MAGIC, 0)#define XXX_IOCXXX2 _IOW(XXX_IOC_MAGIC, 1,int)#define XXX_IOCXXX3 _IOR(XXX_IOC_MAGIC, 2,int)

8.4.2 Linux 字符设备驱动程序的组成 (11) ioctl 函数( 3) Ioctl 函数模板int xxx_ioctl( struct inode *inode, struct f ile *filp, unsigned int cmd, unsigned long arg) { ... switch (cmd) { case XXX_CMD1: ... break; case XXX_CMD2: ... break; default: ///*不能支持的命令 */

return - ENOTTY; } return 0; }

阶段总结

本节介绍了字符设备驱动结构

8.4.3 添加驱动程序到内核中 (1)

配置内核 编译内核 添加驱动程序到内核中

8.4.3 添加驱动程序到内核中 (2)

配置内核 配置命令包括 :

make config make menuconfig make xconfig make gconfig

可通过“上”、“下”、“左”、“右”键移动菜单,选择某项按“ Y”,取消选择按“N”,如果选择某项编译为模块按“M”,进入子菜单按“Enter”,返回上一级菜单按 “Esc”

使用 make config、 make menuconfig等命令后,会生成一个 .config 配置文件 ( 是隐身文件,通过 ls –a才能看到 )

8.4.3 添加驱动程序到内核中 (3)

编译内核 可用如下命令编译内核 :

make ARC=arm CROSS_COMPILE=arm-linux- zImage

也可在源代码根目录的Makefile 中将 ARCH和 CROSS_COMPILE直接指定为 arm和 arm-linux-,如:ARCH ?= armCROSS_COMPILE ?= arm-linux-

这样就没有必要每次编译的时候都指定体系结构和交叉编译器了 , 只须使用下面命令就可以了: make zImage

8.4.3 添加驱动程序到内核中 (4)

添加驱动程序到内核( 1 ) Linux 2.6内核的配置系统由以下 3个部分组成。 Makefile :分布在 Linux 内核源代码中的Makefite ,定义 Linux 内核的编译规则 配置文件 (Kconfig) :给用户提供配置选择的功能。 配置工具:包括配置命令解释器 ( 对配置脚本中使用的配置命令进行解释 ) 和配置用

户界面 ( 提供字符界面和图形界面 ) 。这些配置工具都是使用脚本语言编写的,如 Tcl/TK、 Perl 等。

在 Linux 内核中增加程序需要完成以下 3项工作。 将编写的源代码复制到 Linux 内核源代码的相应目录。 在目录的 Keonfig文件中增加新源代码对应项目的编译配置选项。 在目录的Makefile 文件中增加对新源代码的编译条目。

8.4.3 添加驱动程序到内核中 (5)

添加驱动程序到内核( 2 ) 实例:在内核源代码 drivers 目录下为 ARM体系结构新增 test driver test driver 的树形目录: 步骤:

1 、拷贝 test到 drivers路径下 2、为新增目录创建 Kconfig和Makefile 3、修改新增目录父目录的 Kconfig和

Makefile ,以便新增的 Kconfig和 Makefile 能够被引用 4、在 arch/arm/Kconfig 里增加 source “drivers/test/Kconfig”

8.4.3 添加驱动程序到内核中 (6)

添加驱动程序到内核( 3 ) 步骤:1 、拷贝 test 到 drivers 路径下 cp –fr test linux_kernel_path/drivers2 、为新增目录创建 Kconfig 和 Makefile

8.4.3 添加驱动程序到内核中 (7)

添加驱动程序到内核( 4) 步骤:3、修改新增目录的父目录的 Kconfig和Makefile

在 drivers/Kconig中加入: source "drivers/test/Kconfig“在 drivers/Makefile 中加入: obj-$(CONFIG_TEST) += test/

4、在 arch/arm/Kconfig 里加入: source “drivers/test/Kconfig”增加了 Kconfig和Makefile 文件之后的新的 test树型目录如下所示:

本章总结 (1)

字符设备驱动主要结构和开发方法

字符设备驱动主要结构和开发方法

如何配置、编译内核,以及如何把驱动程序添加到内核

如何配置、编译内核,以及如何把驱动程序添加到内核

如何在用户空间调用驱动

如何在用户空间调用驱动

字符设备驱动基本结构 字符设备驱动基本结构

字符设备驱动程序 字符设备驱动程序

用户空间调用设备驱动程序 用户空间调用设备驱动程序

添加驱动程序到内核 添加驱动程序到内核 内核配置和编译方法内核配置和编译方法

添加驱动程序到内核中添加驱动程序到内核中

主要概念和结构体主要概念和结构体

实例实例

字符驱动的主要组成字符驱动的主要组成

本章总结 (2)

Linux 设备驱动 简介和特点Linux 设备驱动 简介和特点

设备驱动程序简介 设备驱动程序简介

Linux 驱动程序概述 Linux 驱动程序概述

设备驱动的 Hello World 模块设备驱动的 Hello World 模块

内核驱动模块和应用程序对比 内核驱动模块和应用程序对比

编译和装载驱动模块 编译和装载驱动模块

构造和运行模块 构造和运行模块

设备的分类和特点 设备的分类和特点

字符设备字符设备

网络设备网络设备

块设备块设备

Linux 设备驱动 分类和特点Linux 设备驱动 分类和特点

Linux 模块的基本组成部分Linux 模块的基本组成部分

内核模块和应用程序对比内核模块和应用程序对比

如何编译内核模块如何装载和卸载内核模块如何编译内核模块如何装载和卸载内核模块