linux n.
Download
Skip this Video
Loading SlideShow in 5 Seconds..
Linux 系统驱动概述 PowerPoint Presentation
Download Presentation
Linux 系统驱动概述

Loading in 2 Seconds...

play fullscreen
1 / 49

Linux 系统驱动概述 - PowerPoint PPT Presentation


  • 247 Views
  • Uploaded on

Linux 系统驱动概述. 博创科技. Linux 系统驱动概述. 操作系统功能的划分 linux下的设备驱动的特点 Linux 设备的分类 典型的 Linux 字符型设备驱动结构 Linux 驱动中的数据结构 Linux 驱动中常用的操作函数 Linux 下的设备管理的问题. 操作系统功能的划分. 进程管理 内存管理 文件系统 设备控制 网络功能. linux 下的设备驱动的特点. linux 下的设备控制由驱动程序完成: Linux 下对外设的访问只能通过驱动程序 Linux 对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序:

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about 'Linux 系统驱动概述' - stefan


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
linux1
Linux系统驱动概述
  • 操作系统功能的划分
  • linux下的设备驱动的特点
  • Linux设备的分类
  • 典型的Linux字符型设备驱动结构
  • Linux驱动中的数据结构
  • Linux驱动中常用的操作函数
  • Linux下的设备管理的问题
slide3
操作系统功能的划分
  • 进程管理
  • 内存管理
  • 文件系统
  • 设备控制
  • 网络功能
linux2
linux下的设备驱动的特点
  • linux下的设备控制由驱动程序完成:
    • Linux下对外设的访问只能通过驱动程序
    • Linux对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序:

Open、Release、read、write、ioctl…

    • 驱动程序属于内核的一部分,可以使用中断、DMA等操作
    • 驱动程序需要在用户态和内核态之间传递数据,是应用程序与内核及外设通讯的桥梁
    • uClinux下可以在应用层直接访问外设,操作寄存器,但是无法处理中断——不推荐使用
linux3
Linux设备的分类

按照上述系统内核的功能,Linux中把系统的设备定义成如下三类:

  • 字符设备
    • 支持面向字符的I/O操作
    • 使用独立的缓冲区结构
    • 顺序存取数据
  • 块设备
    • 仅支持面向块的I/O操作
    • I/O操作都通过在内核地址空间中的I/O缓冲区进行
    • 随机存取:支持几乎任意长度和任意位置上的I/O请求
  • 网络设备
linux4
典型的Linux字符型设备驱动所包含的内容
  • 初始化函数:xxx_init,向操作系统注册及硬件初始化(包括中断的request_irq和使能)
  • 主体代码 :file_operations里面注册的操作函数
  • 中断处理函数
  • 示例
linux file operations
Linux驱动中的数据结构- file_operations

file_operations数据结构,定义在include/linux/fs.h中,驱动程序很大一部分工作就是要“填写”结构体中定义的函数。

lseek:移动文件指针的位置,只能用于随机存取设备

read:读操作

write:写操作,与read类似

readdir:取得下一目录节点,文件系统相关的设备驱动程序使用

select:选择操作,如果没有提供将会认为设备已经准备好

ioctl:读、写以外的其它操作,参数为自定义的的命令数

mmap:内核空间到用户空间的映射

open:打开设备,为I/O操作做准备

release:即close操作

linux file operations1
Linux驱动中的数据结构- file_operations
  • Open函数

Open方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,此外open操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。在大多数驱动程序中Open方法应完成如下工作:

递增使用计数

1.检查特定设备错误。

2.如果设备是首次打开,则对其进行初始化。

3.识别次设备号,如有必要修改f_op指针。

4.分配并填写filp->private_data中的数据。

linux file operations2
Linux驱动中的数据结构- file_operations
  • Release函数

与open方法相反,release 方法应完成如下功能:

1.释放由open分配的filp->private_data中的所有内容

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

3.使用计数减一

linux file operations3
Linux驱动中的数据结构- file_operations
  • Read和Write函数
  • read 方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。对于者两个方法,参数filp是文件指针,count是请求传输数据的长度,buffer是用户空间的数据缓冲区,ppos是文件中进行操作的偏移量,类型为64位数。由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy之类的函数,必须使用如下函数:
  • unsigned long copy_to_user (void *to,const void *from,unsigned long count);
  • unsigned long copy_from_user(void *to,const void *from,unsigned long count);
linux file operations4
Linux驱动中的数据结构- file_operations
  • ioctl函数
  • ioctl方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过read/write文件操作来完成,比如在UP-NETARM2410-S中的SPI设备通道的选择操作,无法通过write操作控制,这就是ioctl操作的功能。
  • 驱动程序中定义的ioctl 方法原型为:

int (*ioctl) (struct inode *inode, struct file *file,unsigned int cmd,

unsigned long arg)

inode 和 filp两个指针对应应用程序传递的文件描述符fd,cmd不会被修改地传递给驱动程序,可选的参数arg则无论用户应用程序使用的是指针还是其他类型值,都以unsigned long的形式传递给驱动。

linux5
Linux驱动中的数据结构
  • inode结构:
    • 提供关于设备文件的/dev/xxx的信息
  • file结构:
    • 提供关于被打开的文件的信息
linux6
Linux驱动中常用的操作函数
  • 申请中断:
    • int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), unsigned long irq_flags, const char * devname, void *dev_id)
  • 释放中断:
    • void free_irq(unsigned int irq, void *dev_id);
  • 申请内存:void * kmalloc(unsigned int len, int priority);
  • 释放内存:void kfree(void * obj);
  • 访问I/O端口:inb,outb,inw,outw;inl,outl
  • 时钟、定时器有关的系统函数:add_timer,del_timer,init_timer
  • 打开和关闭中断允许:cli(),sti()
  • 内核打印:printk
slide14
设备管理的问题

如今,Linux 支持很多不同种类的硬件。这意味着/dev中都有数百个特殊文件来表示所有这些设备。而且,这些特殊文件中大多数甚至不会映射到系统中存在的设备上

linux7
Linux设备驱动的路径
  • Linux的设备以文件的形式存在于/dev目录下
  • 设备文件是特殊文件,使用ls /dev -l命令可以看到:
    • c-字符设备
    • b-块设备
    • l-符号连接

crw------- 1 root root 10, 7 Aug 31 2002 amigamouse1

crw------- 1 root root 10, 134 Aug 31 2002 apm_bios

brw-rw---- 1 root disk 29, 0 Aug 31 2002 aztcd

lr-xr-xr-x 1 root root 9 Dec 26 05:52 tty0 -> /dev/vc/0

slide16
主设备号和次设备号
  • 主设备号标识设备对应的驱动程序
  • 一个驱动程序可以控制若干个设备,次设备号提供了一种区分它们的方法
  • 系统增加一个驱动程序就要赋予它一个主设备号。这一赋值过程在驱动程序的初始化过程中进行:

对于字符设备:

int register_chrdev(unsigned int major, const char*name,struct file_operations *fops);

  • 对于查看/dev目录下的设备的主次设备号可以使用如下命令:

[/mnt/yaffs]ls /dev -l

crw------- 1 root root 5, 1 Jan 1 00:00 console

crw------- 1 root root 5, 64 Jan 1 00:00 cua0

slide17
动态分配设备号
  • 在Documentation/device.txt文件中可以找到已经静态分配给大部分设备的列表
  • 由于许多数字已经分配了,为新设备选择一个唯一的号码是很困难的
  • 如果调用register_chrdev时的major为零,函数就会选择一个空闲号码并做为返回值返回
slide18
动态分配的问题

动态分配的主设备号不能保证总是一样的,无法事先创建设备节点

  • 可以从/proc/devices读取

cat /proc/devices

  • 利用脚本动态创建设备文件节点
devfs
使用设备文件系统--devfs
  • 在Linux 2.4的内核里引入了devfs来解决linux下设备文件管理的问题
  • 在驱动程序中通过devfs_register()函数创建设备文件系统的节点

其原型为:

devfs_register(devfs_handle_t dir, const char *name, unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info)

  • 系统启动的时候mount设备文件系统
  • 所有需要的设备节点都由内核自动管理。/dev目录下只有挂载的设备
  • 注:在linux-2.6.12之后的内核的解决办法:udev文件系统
  • http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
slide20
使用设备文件系统-- udev
  • udev 完全在用户态 (userspace) 工作,利用设备加入或移除时内核所发送的hotplug 事件 (event) 来工作。
  • 关于设备的详细信息是由内核输出 (export) 到位于 /sys 的 sysfs 文件系统的。所有的设备命名策略、权限控制和事件处理都是在用户态下完成的。
  • 与此相反,devfs 是作为内核的一部分工作的。
mknod
创建设备节点--mknod
  • mknod /dev/xxx c major minor
  • 关于主次设备号: major + minor
  • 上层应用对驱动的调用:
    • fd=open(“/dev/xxx”,...); file_operations xxx_open
    • read(fd,buffer,count);  file_operations xxx_ read
linux8
Linux驱动程序原理
  • fd=open(“/dev/xxx”,...) file_operations xxx_open
  • write(fd,buffer,count) file_operations xxx_ write
  • read(fd,buffer,count) file_operations xxx_ read
linux9
Linux下的驱动加载方式
  • 一种是直接编译到内核,当内核启动之后,新的驱动程序随之运行;
  • 二是编译为模块,动态加载运行
    • 对模块操作需要使用module-utiles:
      • insmod将编译的模块直接插入内核
      • rmmod从内核中卸载模块
      • lsmod显示已安装的模块
    • gcc编译参数: -D__KERNEL__ -DMODULE –I$(KERNELDIR_INCLUDE)
  • 在调试的过程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行的过程就集成进内核。但编译进内核是某些驱动运行的唯一方法。例如:console驱动,flash驱动和对至少一种文件系统的支持等等。
linux10
Linux下的驱动加载方式
  • module_init:insmod时自动调用,负责模块初始化工作。
    • 对应的操作是module_init
  • module_exit:卸载时调用,负责清除工作。
    • 对应的操作是module_exit
  • 参见:kernel/include/linux/init.h
module init 1
module_init(1)

include/linux/init.h中

#define module_init(x) __initcall(x);

#define __initcall(fn) \

static initcall_t __initcall_##fn __init_call = fn

static initcall_t

__initcall_name_of_initialization_routine __init_call = name_of_initialization_routine

module init 2
module_init(2)
  • include/linux/init.h中定义:

#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))

typedef int (*initcall_t)(void);

  • arch/arm/vmlinux-armv.lds.in文件中:

__initcall_start = .;

*(.initcall.init)

__initcall_end = .;

. = ALIGN(4096);

__init_end = .;

  • init/main.c文件中定义了do_initcalls函数
slide27
__init宏

在include/linux/init.h中

对于非模块加载的驱动程序:

#define __init __attribute__ \ ((__section__ (".text.init")))

  • 通过__init,会把函数中的代码放到.text.init段。这个段在系统启动以后会被释放。在系统内核启动以后,会看到:

Freeing init memory: 68K

linux11
Linux下驱动的调试

1、 使用printk函数

printk函数中可以使用附加不同的日志级别或消息优先级

2 、使用/proc文件系统

内核利用它向外输出信息

3、使用ioctl方法

ioctl系统调用会调用驱动的ioctl方法,我们可以通过设置不同的命名号来编写一些测试函数,使用ioctl系统调用在用户级调用这些函数进行调试。

4、使用strace命令进行调试

strace命令是一个功能强大的工具,它可以显示用户空间的程序发出的全部系统调用,不仅可以显示调用,还可以显示调用的参数和用符号方式表示的返回值。

linux12
Linux下的中断驱动
  • 中断概念
    • 基本定义:中断,异常,中断源,中断嵌套,中断优先级等
    • 中断向量:标识中断的无符号数,组成中断向量表。ARM有7个:
      • 复位:当复位电平有效时产生。程序跳转到复位异常处理程序处
      • 未定义指令:遇到不能处理的指令时产生
      • 软件中断:执行SWI指令产生。用于实现系统调用功能
      • 指令预取中止:预取指令的地址不存在,或该地址不允许当前指令访问,发出中止信号,但指令被执行时产生异常
      • 数据中止:数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常
      • IRQ:标准中断请求
      • FIQ:快速中断请求
slide31
ARM的中断过程
  • 中断的进入
    • 将下一条指令的地址存入相应连接寄存器LR
    • 将CPSR复制到相应的SPSR中
    • 根据中断/异常类型,强制设置CPSR的运行模式位
    • 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序
  • 从中断返回
    • 将连接寄存器LR的值减去相应的偏移量后送到PC中
    • 将SPSR复制回CPSR中
    • 如果进入时设置了中断禁止位,那么清除该标志
    • 中断返回
  • 注意:给IRQ脚中断信号前,必须先打开该中断的使能寄存器和正确设置对应的屏蔽寄存器。当这两个寄存器都设置正确了,中断产生了,CPU保存当前程序运行环境,跳到中断入口,ARM芯片一般是0x18地址处。设置好中断向量,中断向量一般是个跳转语句,跳到正式的中断处理过程,在这里可以关闭所有中断,清中断,处理等等,然后退出。某些处理器一定要清中断,否则下次再给中断信号时就没有反应了。
slide32
中断相关函数说明
  • int set_external_irq(int irq, int edge, int pullup);
    • 功能:设置中断线相应的属性,完成注册中断前的设置
    • 参数:
    • irq:需要设置的中断号。可以自定义数值,也可以直接使用kernel/arch/arm/kernel/irqs.h中预定义的值。例如: IRQ_EINT0, IRQ_EINT4_7,IRQ_DMA0等
    • edge:中断触发方式,可以是EXT_LOWLEVEL,EXT_HIGHLEVEL,EXT_FALLING_EDGE,EXT_RISING_EDGE,EXT_BOTH_EDGES
    • pullup:是否拉高。可以设为GPIO_PULLUP_EN和 GPIO_PULLUP_DIS方式。当设为GPIO_PULLUP_EN时,在GPIO没有输入输出时,线路保持高电平。否则始终保持低电平。一般需要设置成拉高状态
  • void enable_irq(unsigned int irq);
    • 参数:
    • irq:set_external_irq中设置的中断线号
    • 功能:使能所选的中断线
  • void disable_irq(unsigned int irq);
    • 参数:
    • 功能:使得所选择的中断线无效
    • 和enable_irq配对使用,实现相反功能
slide33
中断相关函数说明
  • int request_irq(unsigned int irq, void (*handler)(int,void *,struct pt_regs *), unsigned long irq_flags,const char * devname, void * dev_id);
    • 功能:定位中断源,使能中断线和IRQ处理函数
    • irq:外设所使用的中断号
    • handler函数指针:设备驱动程序所实现的ISR函数
    • irqflags:中断请求类型标志,可以是下列值的“或”:
      • SA_SHIRQ:中断是共享的。此时要求参数dev_id必须有效,不能为NULL; IRQ号参数irq不能大于NR_IRQS;且handler指针不能为NULL。否则将出现错误号为-22的参数无效错,无法注册中断服务程序
      • SA_INTERRUPT:当处理中断时,其他局部中断不可用
      • SA_SAMPLE_RANDOM:中断可以作为随机计量单位熵使用
    • devname指针:设备名字字符串
    • dev_id:指向全局唯一的设备标识ID,void类型的指针,供驱动程序自行解释
slide34
中断相关函数说明
    • void free_irq(unsigned int irq, void *dev_id);
    • 功能:释放和中断号绑定的中断处理函数
    • 注意:必须和register_irq()配对使用,它的参数必须和register_irq()里注册的参数一致。dev_id不能是中断服务程序地址,否则执行rmmod删除该将出现错误,必须重启系统才能再次insmod该模块
  • 示例
slide35
功能描述
  • 编写简单的虚拟硬件驱动程序并进行调试
  • 参考驱动代码框架如下,其中的demo_read,demo_write函数完成驱动的读写接口功能,do_write函数实现将用户写入的数据逆序排列,通过读取函数读取转换后的数据。这里只是演示接口的实现过程和内核驱动对用户的数据的处理。demo_ioctl函数演示ioctl调用接口的实现过程。
slide36
static int demo_open(struct inode *inode, struct file *filp)

{

/* 初始化一些资源,如将缓存区清零等。有些资源在设备未打开即未被

* 使用之前最好不要打开,如中断、时钟等。 */

memset(wbuff, 0, BUFFSIZE);

memset(rbuff, 0, BUFFSIZE);

count = 0;

return 0;

}

static int demo_close(struct inode *inode, struct file *filp)

{

return 0;

}

slide37
static ssize_t demo_read(struct file *filp, char *buf, size_t cnt, loff_t *off)

{

if(count < cnt)

cnt = count;

if(cnt)

copy_to_user(buf, rbuff, cnt); /*向应用程序传输数据*/

return cnt;

}

static ssize_t demo_write(struct file *filp, const char *buf,

size_t cnt, loff_t *off)

{

if(cnt > BUFFSIZE)

cnt = BUFFSIZE;

copy_from_user(wbuff, buf, cnt); /*接受来自应用程序的数据*/

count = cnt;

return cnt;

}

slide38
static int demo_ioctl(struct inode *inode, struct file *filp,

unsigned int cmd, unsigned long arg)

{

switch(cmd) {

default:

break;

}

return 0;

}

slide39
static struct file_operations demo_fops = {

owner: THIS_MODULE,

write: demo_write,

read: demo_read,

ioctl: demo_ioctl,

open: demo_open,

release: demo_close,

};

slide40
static int __init demo_init(void)

{

int result;

/* 为设备驱动程序分配资源,比如分配一些内存

*我们分配两小片的缓存区给驱动程序。当向设备写数据时,数据被保存在

*写缓存区(wbuff)中,当从设备读数据时, 数据从读缓存区(rbuff)中读出。驱动程序负责将数据从wbuff写入rbuff。*/

wbuff = kmalloc(BUFFSIZE, GFP_KERNEL);

rbuff = kmalloc(BUFFSIZE, GFP_KERNEL);

/* 注册设备 */

result = register_chrdev(DEMO_MAJOR,, “demo”, &demo_fops);

if(result < 0) { /* 如果设备注册出错,打印错误提示,返回错误代码 */

printk(KERN_ERR “Cannot register demo device.”);

return -EIO;

}

return 0; /* 一般返回0表示没有错误 */

}

slide41
static void __exit demo_exit(void)

{

int result;

/*移除设备*/

result = unregister_chrdev(DEMO_MAJOR, "demo");

if(result < 0) {

printk(KERN_ERR "Cannot remove demo device.\n");

return;

}

/*释放存储区*/

if(wbuff)

kfree(wbuff);

if(rbuff)

kfree(rbuff);

return;

}

MODULE_LICENSE("GPL");

module_init(demo_init); /*标记模块初始化函数*/

module_exit(demo_exit); /*标记模块清除函数*/

test demo c
用户测试程序test_demo.c
  • 编写test_demo.c测试驱动程序
  • 参见实验指导书
slide43
实验步骤
  • 参见实验指导书
devfs1
设备文件系统devfs
  • 2.4版本Linux内核中引入了设备文件系统Device Filesystem(devfs),所有的设备文件作为一个可以挂装的文件系统,这样就可以被文件系统进行统一管理,从而设备文件就可以挂装到任何需要的地方。命名规则也发生了变化,一般将主设备建立一个目录,再将具体的子设备文件建立在此目录下。比如在UP-NETARM2410-S中的MTD 设备为:/dev/mtdblock/0。使用devfs要求内核编译时定义了符号CONFIG_DEVFS_FS
devfs2
与devfs有关的函数

devfs_register函数用于向内核注册设备文件,其原型为:

devfs_handle_t devfs_register(devfs_handle_t dir, const char *name, unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info)

devfs_mk_dir函数用于将设备文件创建在一个特定的目录内,其原型:

devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void *info)

devfs_unregister函数用于移除设备文件,其原型为:

void devfs_unregister(devfs_handle_t deventry);

slide46
参数说明
  • dir新创建的设备文件的父目录,一般设为null, 表示父目录为/dev
  • name设备名称,如想包含子目录,可以直接在名字中包含’/’
  • flagsdevfs标志的位掩码。
  • major主设备号如果在flags参数中指定为DEVFS_FL_AUTO_DEVNUM,则主次设备号就无用了。
  • minor次设备号mode设备的访问模式
  • ops设备的文件操作数据结构指针
  • infofilp->private_data的默认值。
  • deventry指向devfs_register调用后获得的devfs入口
devfs devfs demo
下面在驱动程序里使用devfs,请各位老师根据已给出的程序完成使用devfs的demo程序下面在驱动程序里使用devfs,请各位老师根据已给出的程序完成使用devfs的demo程序
slide48
………………………………………………………………………………………………………………

#ifdef CONFIG_DEVFS_FS

static devfs_handle_t devfs_demo_dir, devfs_demoraw;

#endif

………………………………………………………

static int __init demo_init(void)

{

/*other code here*/

#ifdef CONFIG_DEVFS_FS

devfs_demo_dir = devfs_mk_dir(NULL, "demo", NULL);

devfs_demoraw = devfs_register(devfs_demo_dir, "0", DEVFS_FL_DEFAULT,

DEMO_MAJOR, DEMO_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,

&demo_fops, NULL);

#endif

/*other code here*/

}

static void __exit demo_exit(void)

{

/*other code here*/

#ifdef CONFIG_DEVFS_FS

devfs_unregister(devfs_demoraw);

devfs_unregister(devfs_demo_dir);

#endif

/*other code here*/

dev demo devfs dev demo 0
在驱动模块成功插入后,会在/dev下面建立一个叫做demo的设备文件,若使用devfs,则无需建立设备节点,插入模块后系统会自动建立/dev/demo/0文件节点。在驱动模块成功插入后,会在/dev下面建立一个叫做demo的设备文件,若使用devfs,则无需建立设备节点,插入模块后系统会自动建立/dev/demo/0文件节点。