基于Linux的字符设备驱动程序的设计
- 格式:doc
- 大小:609.50 KB
- 文档页数:24
嵌入式Linux驱动开发教程PDF嵌入式Linux驱动开发教程是一本非常重要和实用的教材,它主要介绍了如何在Linux操作系统上开发嵌入式硬件设备的驱动程序。
嵌入式系统是指将计算机系统集成到其他设备或系统中的特定应用领域中。
嵌入式设备的驱动程序是连接操作系统和硬件设备的关键接口,所以对于嵌入式Linux驱动开发的学习和理解非常重要。
嵌入式Linux驱动开发教程通常包括以下几个主要的内容:1. Linux驱动程序的基础知识:介绍了Linux设备模型、Linux内核模块、字符设备驱动、块设备驱动等基本概念和原理。
2. Linux驱动编程的基本步骤:讲解了如何编译和加载Linux内核模块,以及编写和注册设备驱动程序所需的基本代码。
3. 设备驱动的数据传输和操作:阐述了如何通过驱动程序与硬件设备进行数据的传输和操作,包括读写寄存器、中断处理以及与其他设备的通信等。
4. 设备驱动的调试和测试:介绍了常用的驱动调试和测试技术,包括使用调试器进行驱动程序的调试、使用模拟器进行驱动程序的测试、使用硬件调试工具进行硬件和驱动的联合调试等。
通常,嵌入式Linux驱动开发教程的PDF版本会提供示例代码、实验步骤和详细的说明,以帮助读者更好地理解和掌握嵌入式Linux驱动开发的核心技术和要点。
读者可以通过跟随教程中的示例代码进行实际操作和实验,深入了解和体验嵌入式Linux驱动开发的过程和方法。
总之,嵌入式Linux驱动开发教程是一本非常重要和实用的教材,对于想要在嵌入式领域从事驱动开发工作的人员来说,具有非常重要的指导作用。
通过学习嵌入式Linux驱动开发教程,读者可以系统地了解和学习嵌入式Linux驱动开发的基本原理和技术,提高自己在嵌入式Linux驱动开发方面的能力和水平。
162010,31(1)计算机工程与设计Computer Engineering and Design0引言NVRAM (non-volatile random access memory ,非易失性随机访问存储器)是广泛应用于网络路由器的一种存储器件。
它如同PC 上的CMOS ,作用是存放路由器的配置参数。
目前常见的NVRAM ,大都是静态SRAM ,即带有备用电源的SRAM ,它的实现最简单,同普通内存操作一样。
但是在实际应用中,不是所有的开发板都配备有静态SRAM 。
在这种情况下,如果使用该方案开发网络路由器,重新加入配备电源的SRAM 必须要重新排版,布线。
开发周期与开发成本将会大大增加。
因此,可以考虑在现有的硬件资源基础上,通过新的方式来实现NVRAM [1]。
本文就是以神州龙芯开发的CQ8401开发板为硬件平台,在自行裁剪和移植的嵌入式Linux 平台下,利用Nor Flash 来实现网络路由器的NVRAM 功能。
1NVRAM 新的实现方案分析由于NVRAM 仅用于保存启动配置文件(Startup-Config ),故其容量较小,通常在路由器上只配置32KB~128KB 大小的NVRAM 。
配备电源的SRAM 速度较快,是目前读写最快的存储设备,而成本也比较高。
一般的开发板所配备的Nor Flash空间足够大,在系统性能得到满足的前提下,可以把Nor Flash 分出一个区来当作NVRAM 使用。
SRAM 和Nor Flash 的对比分析,如表1所示。
网络路由器中的NVRAM 用于存放配置参数。
正常启动路由器后,NVRAM 中的内容会拷贝到内存一份,我们对路由器的设置实际上就是修改内存中的参数。
所以内存和NVRAM 中的内容可以不一样,直到使用write memory 将内存设置保存到NVRAM 。
在系统起来以后,我们可以根据需要修改配备参收稿日期:2009-07-17;修订日期:2009-09-18。
基于rk3568的linux驱动开发——gpio知识点基于rk3568的Linux驱动开发——GPIO知识点一、引言GPIO(General Purpose Input/Output)通用输入/输出,是现代计算机系统中的一种常用接口,它可以根据需要配置为输入或输出。
通过GPIO 接口,我们可以与各种外设进行通信,如LED灯、按键、传感器等。
在基于Linux系统的嵌入式设备上开发驱动程序时,熟悉GPIO的使用是非常重要的一环。
本文将以RK3568芯片为例,详细介绍GPIO的相关知识点和在Linux驱动开发中的应用。
二、GPIO概述GPIO是系统中的一个基本的硬件资源,它可以通过软件的方式对其进行配置和控制。
在嵌入式设备中,通常将一部分GPIO引脚连接到外部可编程电路,以实现与外部设备的交互。
在Linux中,GPIO是以字符设备的形式存在,对应的设备驱动为"gpiolib"。
三、GPIO的驱动开发流程1. 导入头文件在驱动程序中,首先需要导入与GPIO相关的头文件。
对于基于RK3568芯片的开发,需要导入头文件"gpiolib.h"。
2. 分配GPIO资源在驱动程序中,需要使用到GPIO资源,如GPIO所在的GPIO Bank和GPIO Index等。
在RK3568芯片中,GPIO资源的分配是通过设备树(Device Tree)来进行的。
在设备树文件中,可以定义GPIO Bank和GPIO Index等信息,以及对应的GPIO方向(输入或输出)、电平(高电平或低电平)等属性。
在驱动程序中,可以通过设备树接口(Device Tree API)来获取这些GPIO资源。
3. GPIO的配置与控制在驱动程序中,首先要进行GPIO的初始化与配置。
可以通过函数"gpiod_get()"来打开指定的GPIO,并判断其是否有效。
如果成功打开GPIO,则可以使用函数"gpiod_direction_output()"或"gpiod_direction_input()"来设置GPIO的方向,分别作为输出或输入。
实验二:字符设备驱动实验一、实验目的通过本实验的学习,了解Linux操作系统中的字符设备驱动程序结构,并能编写简单的字符设备的驱动程序以及对所编写的设备驱动程序进行测试,最终了解Linux操作系统如何管理字符设备。
二、准备知识字符设备驱动程序主要包括初始化字符设备、字符设备的I/O调用和中断服务程序。
在字符设备驱动程序的file_operations结构中,需要定义字符设备的基本入口点。
open()函数;release()函数read()函数write()函数ioctl()函数select()函数。
另外,注册字符设备驱动程序的函数为register_chrdev()。
register_chrdev() 原型如下:int register_chrdev(unsigned int major, //主设备号const char *name, //设备名称struct file_operations *ops); //指向设备操作函数指针其中major是设备驱动程序向系统申请的主设备号。
如果major为0,则系统为该驱动程序动态分配一个空闲的主设备号。
name是设备名称,ops是指向设备操作函数的指针。
注销字符设备驱动程序的函数是unregister_chrdev(),原型如下:int unregister_chrdev(unsigned int major,const char *name);字符设备注册后,必须在文件系统中为其创建一个设备文件。
该设备文件可以在/dev目录中创建,每个设备文件代表一个具体的设备。
使用mknod命令来创建设备文件。
创建设备文件时需要使用设备的主设备号和从设备号作为参数。
阅读教材相关章节知识,了解字符设备的驱动程序结构。
三、实验内容根据教材提供的实例。
编写一个简单的字符设备驱动程序。
要求该字符设备包括open()、write()、read()、ioctl()和release()五个基本操作,并编写一个测试程序来测试所编写的字符设备驱动程序。
linux字符驱动框架(⽤户态的read,write,poll是怎么操作驱动的)前⾔这篇⽂章是通过对⼀个简单字符设备驱动的操作来解释,⽤户态的读写操作是怎么映射到具体设备的。
因为针对不同版本的linux内核,驱动的接⼝函数⼀直有变化,这贴出我测试的系统信息:root@ubuntu:~/share/dev/cdev-2# cat /etc/os-release |grep -i verVERSION="16.04.5 LTS (Xenial Xerus)"VERSION_ID="16.04"VERSION_CODENAME=xenialroot@ubuntu:~/share/dev/cdev-2#root@ubuntu:~/share/dev/cdev-2# uname -r4.15.0-33-generic字符驱动这⾥给出了⼀个不怎么标准的驱动,定义了⼀个结构体 struct dev,其中buffer成员模拟驱动的寄存器。
由wr,rd作为读写指针,len作为缓存buffer的长度。
具体步骤如下:1. 定义 init 函数,exit函数,这是在 insmod,rmmod时候调⽤的。
2. 定义驱动打开函数open,这是在⽤户态打开设备时候调⽤的。
3. 定义release函数,这是在⽤户态关闭设备时候⽤到的。
4. 定义read,write,poll函数,并挂接到 file_operations结构体中,所有⽤户态的read,write,poll都会最终调到这些函数。
chardev.c/*参考:深⼊浅出linux设备驱动开发*/#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/wait.h>#include <linux/semaphore.h>#include <linux/sched.h>#include <linux/cdev.h>#include <linux/types.h>#include <linux/kdev_t.h>#include <linux/device.h>#include <linux/poll.h>#define MAXNUM 100#define MAJOR_NUM 400 //主设备号 ,没有被使⽤struct dev{struct cdev devm; //字符设备struct semaphore sem;int flag;poll_table* table;wait_queue_head_t outq;//等待队列,实现阻塞操作char buffer[MAXNUM+1]; //字符缓冲区char *rd,*wr,*end; //读,写,尾指针}globalvar;static struct class *my_class;int major=MAJOR_NUM;static ssize_t globalvar_read(struct file *,char *,size_t ,loff_t *);static ssize_t globalvar_write(struct file *,const char *,size_t ,loff_t *);static int globalvar_open(struct inode *inode,struct file *filp);static int globalvar_release(struct inode *inode,struct file *filp);static unsigned int globalvar_poll(struct file* filp, poll_table* wait);/*结构体file_operations在头⽂件 linux/fs.h中定义,⽤来存储驱动内核模块提供的对设备进⾏各种操作的函数的指针。
基于嵌入式Linux的LED驱动开发与应用摘要:简要介绍了基于嵌入式ARM处理器芯片LPC3250的嵌入式Linux的LED驱动程序的开发原理、流程以及相关主要接口硬件电路的设计。
实际运行结果表明,该设计完全达到预期效果。
关键词:嵌入式Linux;LED;硬件;驱动程序0引言随着IT技术和嵌入式技术的快速发展,嵌入式产品已经广泛应用于工业、能源、环保、通信等各个行业,显示出其强大的生命力。
Linux是当今流行的操作系统之一,具有源代码开放、内核稳定、功能强大和可裁减等优点而成为众多应用的首选。
同样嵌入式Linux也继承了Linux的诸多优点。
对Linux应用程序来说,由于设备驱动程序屏蔽了硬件的细节,其硬件设备将作为一个特殊的文件,因此应用程序可以像操作普通文件一样对硬件设备进行操作。
本设计中驱动的设备是基于NXP公司的LPC3250微处理器开发的LED信号指示灯,利用这些指示灯来显示仪器的运行状态,方便用户了解仪器的工作状况。
1LPC3250简介及接口电路设计本设计中主控芯片采用LPC3250微处理器,具有高集成度、高性能、低功耗等特点。
它采用90nm工艺和ARM926EJS内核,主频最高为208MHz,具有全系列标准外设。
其中包括带专用DMA控制器的24位LCD控制器,可支持STN和TFT面板。
充分满足本设计的需要,外部只需加入很少芯片就可实现系统功能<sup>[1]</sup>。
LPC3250共有296个管脚。
对于4个LED灯来说需要用到4个引脚,这里使用GPIO端口来设计,GPM1~GPM3作为LED灯的控制端口,另外还需要为LED提供电源,这里需要3.3V的直流电源。
接口电路设计如图1所示。
GPM0~GPM3分别与电阻、LED连接,当GPM0~GPM3置为低电平时,相应的LED灯点亮。
2驱动程序设计在嵌入式Linux操作系统下,有三类主要的设备文件类型:字符设备、块设备和网络设备<sup>[2]</sup>。
字符设备驱动(1)驱动代码完整源码:charButtons.c 内核版本:Linux3.0.8开发板:基于三星S5PV210处理器的Tiny210开发板驱动名称:charButtons.c驱动描述:按键触发中断,中断处理程序执⾏相应的简单LED点亮操作⽅案1注册字符设备使⽤新的接⼝实现(需要好⼏个函数来实现。
貌似更复杂)⽅案2注册字符设备使⽤⽼的接⼝实现(貌似⽼接⼝更简单)/*****************************************************************************简述:简单字符型驱动程序,⼿动静态分配设备号,⼿动创建设备节点******************************************************************************/#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/wait.h>#include <linux/poll.h>#include <linux/sched.h>#include <linux/irq.h>#include <asm/irq.h>#include <linux/interrupt.h>#include <mach/map.h>#include <mach/gpio.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <linux/slab.h>#define DEVICE_NAME "buttons"struct button_desc {int gpio;int number;char *name;};struct led_desc {int gpio;int number;char *name;};static struct button_desc buttons[] = {{ S5PV210_GPH2(0), 0, "KEY0" },{ S5PV210_GPH2(1), 1, "KEY1" },{ S5PV210_GPH2(2), 2, "KEY2" },{ S5PV210_GPH2(3), 3, "KEY3" },{ S5PV210_GPH3(0), 4, "KEY4" },{ S5PV210_GPH3(1), 5, "KEY5" },{ S5PV210_GPH3(2), 6, "KEY6" },{ S5PV210_GPH3(3), 7, "KEY7" },};static struct led_desc leds[] = {{S5PV210_GPJ2(0),1,"LED1"},{S5PV210_GPJ2(1),2,"LED2"},{S5PV210_GPJ2(2),3,"LED3"},{S5PV210_GPJ2(3),4,"LED4"},};#define OK (0)#define ERROR (-1)struct gpio_chip *chip;struct cdev *gDev;struct file_operations *gFile;dev_t devNum;unsigned int subDevNum = 1;//要申请的次设备号个数int reg_major = 234;int reg_minor = 0;static irqreturn_t button_interrupt(int irq, void *dev_id){struct button_desc *bdata = (struct button_desc *)dev_id;int down;unsigned tmp;tmp = gpio_get_value(bdata->gpio);/* active low */down = !tmp;printk("KEY %d: %08x\n", bdata->number, down);if(bdata->number < 4){gpio_set_value(leds[bdata->number].gpio,0);printk("LED %d: On \n",leds[bdata->number].number);}else{gpio_set_value(leds[(bdata->number) - 4].gpio,1);printk("LED %d: OFF \n",leds[(bdata->number)-4].number); }return IRQ_HANDLED;}int butsOpen(struct inode *p, struct file *f){int irq;int i;int err = 0;printk(KERN_EMERG"butsOpen\r\n");for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);// irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"//irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)"err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,buttons[i].name, (void *)&buttons[i]);if (err)break;}for(i = 0; i<ARRAY_SIZE(leds);i++){if(!leds[i].gpio)continue;gpio_direction_output(leds[i].gpio,1);}if (err) {i--;for (; i >= 0; i--) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return -EBUSY;}return0;}int charDrvInit(void){devNum = MKDEV(reg_major, reg_minor);printk(KERN_EMERG"devNum is %d\r\n", devNum);if(OK == register_chrdev_region(devNum, subDevNum, DEVICE_NAME)) {printk(KERN_EMERG"register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}/*if(OK == alloc_chrdev_region(&devNum, subDevNum, subDevNum,"test")) {printk(KERN_EMERG"register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}*/gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);gFile->open = butsOpen;//注册设备函数到file_operations结构体gFile//gDev->owner = THIS_MODULE;gFile->owner = THIS_MODULE;cdev_init(gDev, gFile);//在cdev结构体中添加指针指向file_operations结构体gFilecdev_add(gDev, devNum, 3);//建⽴设备号与cdev结构体联系printk(KERN_EMERG"button driver initial done...\r\n");return0;}void __exit charDrvExit(void){int i,irq;cdev_del(gDev);unregister_chrdev_region(devNum, subDevNum);for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return;}module_init(charDrvInit);//执⾏insmod时会执⾏此⾏代码并调⽤charDrvInit,驱动开始module_exit(charDrvExit);//执⾏rmmod时,结束MODULE_LICENSE("GPL");代码实现⽅案1/*****************************************************************************简述:简单字符型驱动程序,⼿动静态分配设备号,⼿动创建设备节点******************************************************************************/#include <linux/module.h>#include <linux/fs.h>#include <mach/gpio.h>#include <linux/irq.h>#include <linux/kdev_t.h>#include <linux/interrupt.h>#include <linux/init.h>#define DEVICE_NAME "leds"struct button_desc {int gpio;int number;char *name;};struct led_desc {int gpio;int number;char *name;};static struct button_desc buttons[] = {{ S5PV210_GPH2(0), 0, "KEY0" },{ S5PV210_GPH2(1), 1, "KEY1" },{ S5PV210_GPH2(2), 2, "KEY2" },{ S5PV210_GPH2(3), 3, "KEY3" },{ S5PV210_GPH3(0), 4, "KEY4" },{ S5PV210_GPH3(1), 5, "KEY5" },{ S5PV210_GPH3(2), 6, "KEY6" },{ S5PV210_GPH3(3), 7, "KEY7" },};static struct led_desc leds[] = {{S5PV210_GPJ2(0),1,"LED1"},{S5PV210_GPJ2(1),2,"LED2"},{S5PV210_GPJ2(2),3,"LED3"},{S5PV210_GPJ2(3),4,"LED4"},};#define OK (0)#define ERROR (-1)dev_t devNum;unsigned int subDevNum = 1;//要申请的次设备号个数int reg_major = 234;int reg_minor = 0;static irqreturn_t button_interrupt(int irq, void *dev_id){struct button_desc *bdata = (struct button_desc *)dev_id;int down;unsigned tmp;tmp = gpio_get_value(bdata->gpio);/* active low */down = !tmp;printk("KEY %d: %08x\n", bdata->number, down);if(bdata->number < 4){gpio_set_value(leds[bdata->number].gpio,0);printk("LED %d: On \n",leds[bdata->number].number);}else{gpio_set_value(leds[(bdata->number) - 4].gpio,1);printk("LED %d: OFF \n",leds[(bdata->number)-4].number); }return IRQ_HANDLED;}int butsOpen(struct inode *p, struct file *f){int irq;int i;int err = 0;printk(KERN_EMERG"butsOpen\r\n");for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);// irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"//irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)"err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_FALLING, buttons[i].name, (void *)&buttons[i]);if (err)break;}for(i = 0; i<ARRAY_SIZE(leds);i++){if(!leds[i].gpio)continue;gpio_direction_output(leds[i].gpio,1);}if (err) {i--;for (; i >= 0; i--) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return -EBUSY;}return0;}static const struct file_operations gFile ={.owner = THIS_MODULE,.open = butsOpen,};int charDrvInit(void){devNum = MKDEV(reg_major, reg_minor);printk(KERN_EMERG"devNum is %d\r\n", devNum);if(OK == register_chrdev(reg_major, DEVICE_NAME, &gFile)){printk(KERN_EMERG "register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}printk(KERN_EMERG "button driver initial done...\r\n");return0;}void __exit charDrvExit(void){int i,irq;unregister_chrdev(reg_major, DEVICE_NAME);for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return;}module_init(charDrvInit);//执⾏insmod时会执⾏此⾏代码并调⽤charDrvInit,驱动开始module_exit(charDrvExit);//执⾏rmmod时,结束MODULE_LICENSE("GPL");MODULE_AUTHOR("LiuB");代码实现⽅案2函数修饰符__init,本质上是⼀个宏定义,在内核源代码中定义:#define __init __section(.init.text) __cold notrace作⽤就是,将被它修饰的函数放⼊.init.text段中去。
基于Linux的字符设备驱动程序的设计1 选题意义驱动程序在 Linux 内核里扮演着特殊的角色. 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口.它们完全隐藏了设备工作的细节. 用户的活动通过一套标准化的调用来进行,这些调用与特别的驱动是独立的; 设备驱动的角色就是将这些调用映射到作用于实际硬件的和设备相关的操作上. 这个编程接口是这样, 驱动可以与内核的其他部分分开建立, 并在需要的时候在运行时"插入". 这种模块化使得 Linux 驱动易写, 以致于目前有几百个驱动可用.尽管编写设备代码并不一定比编写应用程序更困难,但它需要掌握一些新函数库,并考虑一些新问题,而这些问题是在应用程序空间里不曾遇到的。
在应用程序空间写程序,内核能够为犯的一些错误提供一张安全网,但当我们工作在内核空间时,这张安全网已不复存在。
因为内核代码对计算机有绝对的控制权,它能够阻止其他任何进程的执行,所以编写的设备代码绝对小心不能滥用这种权利。
在 Linux 设备驱动中,字符设备驱动较为基础,所以本次实验设计一个简单的字符设备驱动程序,然后通过模块机制加载该驱动,并通过一个测试程序来检验驱动设计的正确与否,并对出现的问题进行调试解决。
2 技术路线模块实际上是一种目标对象文件(后缀名为ko ),没有链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可以动态扩充内核的功能。
模块有一个入口(init_module())和一个出口(exit_module())函数,分别是模块加载和卸载时执行的操作,加载模块使用insmod命令,卸载使用rmmod命令。
字符设备以字节为单位进行数据处理,一般不适用缓存。
大多数字符设备仅仅是数据通道,只能按照顺序读写。
主设备号表示设备对应的驱动程序,次设备号用来区分具体设备的实例。
LINUX为文件和设备提供一致的用户接口,对用户来说,设备文件与普通文件并无区别,设备文件也可以挂接到任何需要的地方。
对于字符设备而言,file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux 的open()、write()、read()、close()等系统调用时最终被调用。
驱动程序的三层界面:驱动程序与操作系统内核的接口,通过file_operations数据结构来完成;驱动程序与系统引导的接口,这部分驱动程序对设备进行初始化;驱动程序与设备的接口,描述驱动程序如何与设备进行交互,这与具体设备密切相关。
3 详细设计3.1 cdev 结构体本论文基于虚拟的globalmem设备进行字符设备驱动,globalmem 意味着“全局内存”,在globalmem字符设备驱动中会分配一片大小为GLOBALMEM_ SIZE(4KB)的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过Linux 系统调用访问这片内存。
在 Linux 2.6 内核中使用cdev结构体描述字符设备,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结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低20位为次设备号。
使用下列宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)MINOR(dev_t dev)而使用下列宏则可以通过主设备号和设备号生成dev_t。
MKDEV(int major, int minor)cdev 结构体的另一个重要成员file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux 2.6 内核提供了一组函数用于操作cdev结构体,如下所示:void cdev_init(struct cdev *, struct file_operations *); struct cdev *cdev_alloc(void);void cdev_put(struct cdev *p);int cdev_add(struct cdev *, dev_t, unsigned);void cdev_del(struct cdev *);cdev_init()函数用于初始化cdev 的成员,并建立cdev 和file_operations 之间的连接,其源代码如下所示。
void cdev_init(struct cdev *cdev, struct file_operations *fops){memset(cdev, 0, sizeof *cdev);INIT_LIST_HEAD(&cdev->list);cdev->kobj.ktype = &ktype_cdev_default;kobject_init(&cdev->kobj);cdev->ops = fops; /*将传入的文件操作结构体指针赋值给cdev的ops*/}cdev_alloc()函数用于动态申请一个cdev内存,其源代码如代码如下所示:struct cdev *cdev_alloc(void){struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL); /*分配cdev的内存*/if (p) {memset(p, 0, sizeof(struct cdev));p->kobj.ktype = &ktype_cdev_dynamic;INIT_LIST_HEAD(&p->list);kobject_init(&p->kobj);}return p;}cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。
对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
3.2 分配和释放设备号在调用cdev_add() 函数向系统注册字符设备之前,应首先调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号这两个函数的原型如下:int register_chrdev_region(dev_t from, unsigned count, const char*name);int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);register_chrdev_region() 函数用于已知起始设备的设备号的情况;而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况。
函数调用成功之后,会把得到的设备号放入第一个参数dev 中。
alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。
相反地,在调用cdev_del() 函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:void unregister_chrdev_region(dev_t from, unsigned count);3.3 file_operations结构体file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux 的open()、write()、read()、close()等系统调用时最终被调用。
3.4 Linux字符设备驱动的组成在 Linux 系统中,字符设备驱动由如下几个部分组成。
3.4.1.字符设备驱动模块加载与卸载函数在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 的注册,而在卸载函数中应实现设备号的释放和cdev的注销。
我们通常习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的cdev、私有数据及信号量等信息。
常见的设备结构体、模块加载和卸载函数形式如下所示://设备结构体struct xxx_dev_t{struct cdev cdev;...} xxx_dev;//设备驱动模块加载函数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); //注册设备 ...}/*设备驱动模块卸载函数*/static void _ _exit xxx_exit(void){unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号 cdev_del(&xxx_dev.cdev); //注销设备...}3.4.2.字符设备驱动的file_operations 结构体中成员函数file_operations 结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux 进行系统调用最终的落实者。
大多数字符设备驱动会实现read()、write()和ioctl()函数,常见的字符设备驱动的这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 *filp, const char _ _user *buf, size_tcount, loff_t *f_pos){...copy_from_user(..., buf, ...);...}/* ioctl函数*/int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){...switch (cmd){case XXX_CMD1:...break;case XXX_CMD2:...break;default:/* 不能支持的命令*/return - ENOTTY;}return 0;}设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count 是要读的字节数,f_pos 是读的位置相对于文件开头的偏移。