从数据关联的角度剖析字符设备驱动中的初始化函数

2019-05-24 14:12刘海秋马慧敏江朝晖
电脑知识与技术 2019年7期
关键词:嵌入式系统

刘海秋 马慧敏 江朝晖

摘要:嵌入式系统的应用覆盖了消费类电子、智能家居、汽车电子等诸多领域,字符设备驱动是嵌入式系统课程教学中的重点和难点内容,其中初始化函数作为向Linux内核加载驱动程序的入口函数,实现了字符设备、设备文件和设备驱动之间的相互关联。介绍了有关初始化函数的关键数据结构体,深入剖析了关键数据结构体之间的信息传递模式和关系建立方法,并给出了初始化函数的典型代码结构。本研究有助于电子信息工程等相关专业的学生对字符设备驱动程序中初始化函数的理解,提高学生在嵌入式系统应用开发中的实践能力。

关键词:嵌入式系统;字符设备驱动;初始化函数;数据关联

中图分类号:G642 文献标识码:A

文章编号:1009-3044(2019)07-0013-04

Abstract:Embedded systems have been applied to many fields, such as consumer electronics, intelligent households and automotive electronics. Character device drivers are the key and difficult contents in embedded systems teaching. Initialization functions, as entries into the Linux kernel, implement the correlation between character devices, device files and device drivers. In this study, pivotal data structures associated with initialization functions are first introduced, and information transfer mode and the relation establishment method between the pivotal data structures are then analyzed, finally, the typical code structures of initialization functions are present. This study is expected to help students in electronic information engineering and other related majors understand the initialization function in the character device drivers and improve their practical abilities in the application and development of embedded systems.

Key words:Embedded systems;Character device drivers;Initialization functions;Data association

1 引言

近年来,嵌入式系统广泛应用于消费类电子、智能家居、汽车电子、医疗电子等诸多领域[1],几乎人手一部的智能手机是嵌入式系统的一项典型应用,嵌入式系统电子产品正在改变人们生活和工作方式,同时,也激发了社会对嵌入式系统开发高级人才的强烈需求[2-5]。为此,各大高校的电子信息工程、自动控制、通信工程等相关专业相继开设了“嵌入式系统”课程,以培养学生掌握嵌入式系统开发技术,适应社会对嵌入式人才的需求[6-8]。

字符设备驱动囊括了硬件架构、操作系统、文件系统以及编程语言等相关知识点,具有极强的综合性、应用性和工程性[9],是嵌入式系统教学的重点和难点内容。初始化函数作为向内核加载驱动程序的入口函数,实现了关联字符设备、设备文件和设备驱动等重要功能[10],是学习字符设备的驱动程序的入门知识。文章从关键数据结构之间信息传递模式的角度,剖析了字符设备驱动中的初始化函数的工作机理,期望有助于学生正确梳理字符设备、设备文件和设备驱动三者之间的内在关联,掌握初始化函数的代码结构,并应用于课程实践中。

2 字符设备驱动的典型结构介绍

一方面,加载后的驱动程序以独立模块的形式存在于Linux內核中,字符设备驱动遵循模块的代码结构。另一方面,Linux内核抽象了对硬件的处理,在应用层,对设备的操作处理与普通文件相同,只需调用内核提供的开关、读写等系统函数,便可以实现相应操作;与普通文件不同的是,在内核层,将有关文件操作的开关、读写等函数封装到file_operation结构体中,而函数的具体实现由驱动程序完成,因此,字符设备驱动程序除了包含模块必需的代码之外,还包括file_operation结构体中相关等函数的具体实现。

综合以上两方面,字符设备驱动的典型代码结构如下:

static int xxx_open() //打开

static int xxx_release()//关闭

static ssize_t xxx_read()//读

static ssize_t xxx_write()//写

static int _init xxx_init( )//初始化函数

static void _exit xxx_exit( ) //退出函数

module_init( xxx_init ) //入口设置

module_exit( xxx_exit ) //出口设置

3 初始化函数中数据关联的建立

3.1 关键数据结构体的介绍

字符设备驱动程序通常涉及三个关键的数据结构体:cdev、inode和file_operations结构体,实现该三个结构体之间关联的正确建立是字符设备驱动程序中初始化函数的基本任务。

3.1.1 cdev结构体

Linux内核提供了cdev结构体,实现对字符设备的抽象。cdev结构体中包含了字符设备的设备号、文件操作函数指针等关键信息。cdev结构体的关键成员如下:

struct cdev {

const struct file_operations *ops; // 文件操作結构体

dev_t dev; //设备号

unsigned int count; //设备数目

};

在字符设备初始化函数中,需要完成cdev结构体的初始化、关联cdev与该设备对应的文件操作函数、将cdev结构体插入到内核中的字符设备列表中。

3.1.2 inode结构体

Linux内核提供了inode结构体,封装与文件操作有关的信息,inode结构体的关键成员如下:

struct inode {

dev_t i_rdev;//设备文件的设备号

struct cdev *i_cdev;//字符设备cdev结构体指针

};

其中的设备号i_rdev和指向cdev结构体的指针*i_cdev是与字符设备驱动开发关系最大的两个成员。

3.1.3 file_operations结构体

file_operations结构体集合了大量的成员函数,用以实现文件的开关、读写等操作,其中与字符设备驱动密切相关的函数包括:

struct file_operations {

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中读取数据

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据

int (*open) (struct inode *, struct file *); //打开

int (*release) (struct inode *, struct file *); //关闭

};

在应用中,根据需要选取一部分成员函数进行实现即可,成员函数的具体实现是设备驱动程序设计的核心内容。

3.2 数据结构体之间关联的分析

字符设备涉及三个基本概念:设备、设备文件和驱动程序,三者之间通过设备号关联,如图1所示。当应用程序访问设备文件时,内核根据文件名确定相应的设备号,找到对应的驱动程序,执行驱动程序中的函数,当函数中涉及控制具体设备时,内核根据设备号找到相关的字符设备,实现设备的控制。实际上,内核是通过设备号关联设备、设备文件以及驱动程序的。

为了实现设备、设备文件和驱动程序三者之间的关联,在驱动程序开发过程中需要完成以下六个步骤,如图2所示。

3.2.1 申请设备号并关联设备号和驱动程序

设备号分为主设备号和次设备号,主设备号用以区分设备类型,次设备号用以区分同一类型设备中的不同个体。通过设备类型和设备号能够对设备进行唯一标示。因此,必须为每个设备向内核申请独立的设备号。

此外,对设备的具体操作由相应的驱动程序实现,Linux内核通过设备号正确地找到对应的驱动程序,是顺利完成设备操作的首要前提。因此,需要将驱动程序与设备号相互关联。

3.2.2 创建字符设备cdev

Linux内核采用cdev结构体来表征一个字符设备,当添加新的字符设备之后,需要为新加的字符设备创建cdev结构体,该结构体中包含了设备号和操作函数指针。

3.2.3 关联字符设备cdev与设备号并将字符设备插入字符设备列表中

Linux内核中存在字符设备列表,如图2所示,当内核访问某设备时,以设备号为索引遍历设备列表,确定相应的表项,表项中保存了该设备的指针。因此,若要字符设备能够被内核访问,必须将字符设备的cdev结构体与设备号相关联,并以设备号为索引,将该字符设备的指针保存到字符设备列表相应的表项中。

3.2.4 关联字符设备cdev和file_operation结构体

Linux内核为每个设备文件分配了表征文件属性的inode结构体和文件操作结构体file_operations。当内核对设备进行操作时,需要访问相应的file_operation结构体,为了使内核正确地找到file_operation结构体,必须将字符设备的cdev和file_operation相互关联。

3.2.5 关联file_operation中的成员函数和驱动程序中的函数

对字符设备的操作主要采用file_operation结构体中的open、release、read等成员函数,这些函数在驱动程序中实现。当应用程序调用file_operation结构体中的成员函数时,实际上调用的是驱动程序中的相关函数。因此,必须将file_operation结构体中的成员函数和驱动程序中的函数相互关联。

3.2.6 创建设备文件并关联设备文件和设备号

Linux内核将设备抽象成文件的形式,使应用程序对设备的访问如同访问普通文件,因此,需要创建设备文件。此外,为了使应用程序访问设备文件时,内核能够根据设备文件名正确地找到与之对应的设备以及其驱动程序,必须将设备文件和能够唯一标示设备的设备号关联起来。

3.3 数据结构体之间关联的建立

3.3.1 申请设备号并关联设备号和驱动程序的实现

通过向内核注册驱动程序,实现设备号的申请,以及设备号与驱动程序之间的相互关联。设备号的分配方式分为两种:静态分配和动态分配,静态分配是由用户指定主设备号,由内核检查设备号的可用性,可用则返回零,不可用则返回负数。动态分配是由内核根据设备号的实用情况,为设备分配空闲的设备号。

Linux内核提供的用于静态分配设备号的注册函数为:

register_chrdev_region(dev_t first, unsigned int count, char *name)

参数说明如下:first : 需用户指定的主设备号; count:次设备号的个数;name:相关联的驱动名称。返回值说明如下: 成功:返回值为0;失败:返回值小于0。

Linux内核提供的用于动态分配设备号的注册函数为:

int alloc_chrdev_region (dev_t dev, unsigned int baseminor, unsigned int count, char name)

参数说明如下:dev:输出型参数,获得分配到的设备号;baseminor :次设备号的基准;其余参数同上。返回值说明如下: 成功:分配得到的设备号由dev带出来;失败:返回值小于0。

当用户不需要某个设备时,可以注销设备驱动程序,Linux内核提供的注销函数如下:

unregister_chrdev_region (dev_t first, unsigned int count)

参数说明同上。

3.3.2 创建字符设备cdev结构体的实现

用户可以采用如下语句定义一个字符设备my_cdev结构体对象,之后Linux内核会为该结构体分配存储空间,完成字符设备my_cdev的创建。

struct cdev my_cdev

3.3.3 关联字符设备cdev与设备号的实现

Linux内核提供了cdev_add函数,通过将设备号赋值给cdev结构体中的dev成员,实现字符设备cdev与设备号之间的相互关联,利用设备号索引字符设备列表中的表项,并将存储字符设备cdev结构体的地址赋给该表项,以便于内核通过设备号正确地找到字符设备cdev结构体。

cdev_add函数的定义为:

cdev_add (cdev *, dev_t, count)

參数说明如下:dev_t:设备号;cdev * :字符设备结构体的指针;count : 设备号的个数。返回值说明如下:成功:返回值为0;失败:返回值小于0。

该函数以内核分配的设备号为输入参数,因此,在字符设备驱动程序的初始化函数中,该函数必须位于注册函数的后面。

3.3.4 关联字符设备的cdev结构体和file_operation结构体的实现

Linux内核提供了cdev_init函数,通过将存储文件操作结构体file_operations的地址赋给字符设备cdev结构体中的ofp成员,实现字符设备cdev和文件操作file_operation的相互关联,以便于Linux内核正确地找到与字符设备cdev对应的file_operation。

该函数的定义为:

cdev_init (cdev *, file_operations *)

参数说明如下:cdev*: 字符设备结构体的指针;file_operations*: 文件操作函数的指针。

3.4.5 关联file_operation结构体中的成员函数和驱动程序中函数的实现

通过在驱动程序中添加填充函数,明确file_operation结构体中的成员函数和驱动程序中函数之间的对应关系,进而实现两者的关联。

填充函数的代码结构如下:

file_operations xxx_ops =

{ .open = xxx_ open,

.release = xxx_ release,

.read = xxx_read,

.write = xxx_write,

. ioctl = xxx_ioctl,

…}

该填充函数以驱动程序中的xxx_open、xxx_release、xxx_read等函数为参数,因此,在字符设备驱动程序中,填充函数必须位于上诉函数定义的后面。

3.3.6 创建设备文件并关联设备文件和设备号的实现

Linux内核提供了mknod命令,为指定类型的设备创建设备文件,并将设备文件与设备号相互关联,以便于应用程序访问设备文件时,内核能够根据设备文件名正确地找到与之对应的设备号,进而确定该设备的驱动程序。

mknod命令的具体格式如下:

mknod /dev/xxx c M m

参数说明如下:/dev/xxx:创建的设备文件;c:字符设备类型;M:主设备号;m:次设备号。

值得注意的是,当驱动程序的初始化函数中采用动态分配设备号时,每次驱动程序加载内核分配的设备号可能不同,为了在运用mknod命令时,输入正确的设备号,必须在成功加载了字符设备驱动程序之后,采用cat命令查看所加载的字符设备的主、次设备号,而后才能运用mknod上述命令创建设备文件。

4 初始化函数的典型结构

综合上述数据结构体之间关联的建立方法,形成如下所示的字符设备驱动程序中初始化函数的典型结构。

static int major = 250; //主设备号

static int minor = 0; //次设备号

static dev_t devno; //设备号

static struct cdev mycdev; //创建1个字符设备

static int _init xxx_init(void) //初始化函数

{

int ret;

devno = MKDEV(major,minor); // 由主设备号和次设备号构成设备号

ret = register_chrdev_region(devno, 1, "xxx"); //注册驱动程序

cdev_init(&mycdev,&xxx_ops); //将设备和file_operation关联

ret = cdev_add(&mycdev,devno,1);//向内核添加mycdev

return 0;

}

static void _exit xxx_exit (void) //退出函數

{

cdev_del(&mycdev); //从内核中移除mycdev

unregister_chrdev_region(devno,1); // 注销驱动程序

}

module_init( xxx_init ) //入口函数设置

module_exit( xxx_exit ) //出口函数设置

Linux内核为加载模块提供了insmod命令,加载后的驱动从所设置的入口函数开始执行,通常将初始化函数设置为入口函数,从而实现了向内核申请设备号、关联设备号和驱动程序等一系列功能。

5 结论

字符设备驱动设计是电子信息工程等专业课程“嵌入式系统”的重要内容,是嵌入式系统开发的必备技术,其中的初始化函数涉及设备、文件系统、Linux内核等多个知识点,是字符设备驱动设计中的难点。文章从数据关联的角度,深入剖析了关键数据结构体之间的信息传递模式和关系建立方法,并给出了典型的代码结构。文章研究结果有助于电子信息工程等相关专业的学生对字符设备驱动初始化函数的理解,提高学生在综合实验、毕业设计等应用开发中的实践能力。

参考文献:

[1] 朱铭琳.嵌入式系统开发课程教学改革[J]. 信息与电脑(理论版),2017(19):231-233.

[2] 黎芳芳,柴明钢,刘先锋. 以创新应用能力为核心嵌入式系统教学的改进探索[J]. 时代教育,2017(19):52.

[3] 杜钦生, 唐伎玲. 应用型大学嵌入式系统人才培养模式研究[J]. 长春大学学报, 2012, 22(2):214-216.

[4] 谢斌, 任克强, 钟文涛. 嵌入式系统应用型人才培养模式的探讨[J]. 江西理工大学学报, 2010, 31(2):93-95.

[5] 张晓东, 卢涛, 曹毅. 应用型嵌入式系统人才培养模式改革与探索[J]. 中国轻工教育, 2017(2):56-58.

[6] 李文生,邓春健,吕燚. 案例驱动的嵌入式系统教学改革探索[J]. 计算机教育,2011(2):22-25.

[7] 郭国法, 宫瑶, 张开生. 嵌入式课程递阶教学平台的设计与实现[J]. 电脑知识与技术, 2015(11).

[8] 李岩,王小玉,孙永春. 嵌入式系统教学研究[J]. 电气电子教学学报,2006(3):45-47+90.

[9] 周菁. 嵌入式Linux设备驱动程序设计探讨[J]. 电脑编程技巧与维护, 2016(4):47-48.

[10] 陈文智. 嵌入式系统原理与设计[M]. 清华大学出版社, 2011.

【通联编辑:王力】

猜你喜欢
嵌入式系统
Teaching Research on IoT and—Embedded System of Software Engineering
面向应用的智能专业嵌入式系统教学
办公自动化系统的设计
嵌入式系统课程“中断、异常与事件”教学实践及启示
面向实践创新人才培养的嵌入式系统教学研究