原标题:Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动

2.3 函数实现机制

2.3.1 Linux 字符设备驱动

在linux 3.5.4中,用结构体cdev描述字符设备,cdev结构体在文件include/linux/cdev.h中定义,如下所示:

struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

类型

字段

说明

struct kobject

kobj

设备在底层的统一接口,是设备模型的核心结构,每个cdev结构体都有一个kobject,每个在内核中注册的kobject对象都对应于sysfs文件中的一个目录

struct module *

owner

指向实现驱动的模块

struct file_operations *

ops

操作设备文件的方法,实现与具体硬件通信的操作,也是字符设备驱动程序的主体部分

struct list_head

list

实现一个双向链表,其中包含表示该设备的设备文件的inode

dev_t

dev

表示设备号,一个设备号由主设备号和次设备号组成

unsigned int

count

表示与该设备关联的次设备号的数目

表21结构体cdev 各字段说明

flle_operations结构体中定义了操作设备文件的方法,都是一些函数指针,其在/include/linux/fs.h文件中定义如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

. . . . . . .

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

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

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, loff_t, loff_t, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

int (*check_flags)(int);

. . . . . . . .

};

字段名

说明

owner

指向拥有该结构体的驱动模块指针

llseek

用于修改文件读写位置

read

从字符设备中读取数据

write

向字符设备发送数据

aio_read

初始化一个异步的读取操作

aio_write

初始化一个异步的写入操作

readdir

用于读取目录,对于字符设备文件,该字段为NULL

unlocked_ioctl

执行设备I/O控制命令

mmap

用于将设备内存映射到进程地址空间

open

打开操作

release

关闭操作

synch

刷新待处理的数据

lock

文件锁操作

check_flags

标志位检查

表2-2 file_operations结构体字段说明

内核中所有已分配的字符设备编号都记录在一个名为chrdevs散列表里。该散列表中的每一个元素是一个char_device_struct结构,内核并不是为每一个字符设备编号定义一个char_device_struct结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个char_device_struct结构。chrdevs散列表的大小是255。char_device_struct结构在文件/fs/char_dev.c中定义,如下所示:

static struct char_device_struct {

struct char_device_struct *next;

unsigned int major;

unsigned int baseminor;

int minorct;

char name[64];

struct cdev *cdev;/* will die */

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

类型

字段

说明

struct char_device_struct*

next

指向散列链表中的下一个元素的指针

unsigned int

major

主设备号

usigned int

baseminor

次设备号范围中的起始设备号

int

minorct

次设备号范围

char

name[64]

与设备编号关联的设备驱动名称

struct cdev*

cdev

指向字符设备描述结构体的指针,在未来内核版本中会删除此字段

表2-3 char_device_struct结构个字段说明

用于操作cdev结构体的函数的具体实现在文件/fs/char_dev.c中,cdev_init()函数用于初始化cdev成员,并建立cdev和file_operations之间的连接实现,代码如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

{

memset(cdev, 0, sizeof *cdev);

INIT_LIST_HEAD(&cdev->list);

kobject_init(&cdev->kobj, &ktype_cdev_default);

cdev->ops = fops;

}

cdev_init()函数执行流程如下:

(1)调用memset()函数初始化cdev结构体实例。

(2)初始化cdev结构体实例链表。

(3)初始化kobject结构体。

(4)将cdev结构体实例与对应file_operations结构体对应。

cdev_alloc()函数用于动态申请一个cdev内存,具体代码在文件fs/char_dev.c中,代码如下:

struct cdev *cdev_alloc(void)

{

struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);

if (p) {

INIT_LIST_HEAD(&p->list);

kobject_init(&p->kobj, &ktype_cdev_dynamic);

}

return p;

}

cdev_alloc()函数执行流程如下:

(1)调用kzalloc()给cdev结构体分配空间。

(2)如果分配空间成功,初始化cdev结构体实例链表并初始化kobject结构体。

(3)返回cdev结构体指针

cdev_add()函数用于向系统添加一个cdev对象,完成字符设备的注册,对cdev_add()的调用通常发生在字符设备驱动模块加载函数中。在调用cdev_add()函数前,要首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号。具体代码在文件fs/char_dev.c中,代码如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

{

p->dev = dev;

p->count = count;

return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

}

cdev_add执行流程如下:

(1)初始化cdev结构体中的dev、count字段,dev是设备号,count是设备号范围,及与该设备驱动相关的次设备号范围。

(2)调用kobj_map()函数,返回结果。kobj_map()用于用来把字符设备编号和cdev 结构变量一起保存到cdev_map这个散列表里。当后续要打开一个字符设备文件时,通过调用kobj_lookup()函数,根据设备编号就可以找到cdev结构变量,从而取出其中的ops字段

cdev_del()函数用于删除一个cdev对象,完成字符设备的销毁,通常在字符设备驱动模块卸载函数中调用,在调用cdev_del()函数前,要调用unregister_chrdev_region()释放原先申请的设备号。具体代码在文件fs/char_dev.c中,代码如下。

void cdev_del(struct cdev *p)

{

cdev_unmap(p->dev, p->count);

kobject_put(&p->kobj);

}

cdev_del()执行流程如下:

(1)调用cdev_unmap()函数从cdev_map散列表中删除cdev设备实例和相应设备编号。

(2)将kobject结构体实例数量减一。

register_chrdev_region()函数用于向系统申请设备号,函数有三个参数:第一个为初始的设备号,第二个是请求的设备号的大小,即次设备号的大小,第三个参数是这个范围(次设备号大小)内的设备号对应的设备驱动程序。该函数先执行个for循环检查请求的设备号范围是否超过了次设备号范围,然后在每个设备号范围上调用__register_chrdev_region()函数,函数执行成功返回0,错误返回错误代码。具体代码在文件fs/char_dev.c中,代码如下:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

{

struct char_device_struct *cd;

dev_t to = from + count;

dev_t n, next;

for (n = from; n < to; n = next) {

next = MKDEV(MAJOR(n)+1, 0);

if (next > to)

next = to;

cd = __register_chrdev_region(MAJOR(n), MINOR(n),

next - n, name);

if (IS_ERR(cd))

goto fail;

}

return 0;

fail:

to = n;

for (n = from; n < to; n = next) {

next = MKDEV(MAJOR(n)+1, 0);

kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

}

return PTR_ERR(cd);

}

register_chrdev_region()函数执行流程如下:

(1)通过MKDEV()函数得到一个设备号存储于next中。

(2)判断在from的基础上再追加count个设备是否已经溢出到下一个主设备号,。如果溢出(next大于to),那么整个for语句就只执行个一次__register_chrdev_region函数;否则当设备号没溢出时,会循环调用__register_chrdev_region函数,直到next大于to。

(3)如果在某个小范围调用__register_chrdev_region时出现了失败,那么会将此前分配的设备号都释放。

alloc_chrdev_region()函数也是向系统申请设备号,但相比register_chrdev_region()函数,其可以动态分配一个主设备号。函数有四个参数:第一个为初始设备号,第二个是请求分配的次设备号的初始值,第三个是次设备号的范围,第四个是次设备号对应的驱动程序的名称,此函数最终调用__register_chrdev_region()函数实现。具体代码在文件fs/char_dev.c中,如下所示:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

const char *name)

{

struct char_device_struct *cd;

cd = __register_chrdev_region(0, baseminor, count, name);

if (IS_ERR(cd))

return PTR_ERR(cd);

*dev = MKDEV(cd->major, cd->baseminor);

return 0;

}

register_chrdev()函数是一个老式分配设备编号范围的函数,其是__register_chrdev()函数的封装,__register_chrdev()函数内部调用__register_chrdev_region()函数实现。其分配一个单独主设备号和0 ~ 255的次设备号范围。如果申请的主设备号为0则动态分配一个。该函数还需传入一个file_operations结构的指针,函数内部自动分配了一个新的cdev结构。

resgister_chrdev_region()、alloc_chrdev_region()、register_chrdev()三个字符设备注册函数最终都调用了__register_chrdev_region()函数。__register_chrdev_region()函数用于向系统注册一个设备号,有四个参数,第一个是设备的主设备号,第二个是初始此设备号,第三个是次设备号范围,第四个是与设备号对应的设备驱动名称。__register_chrdev_region()函数代码在文件/fs/char_dev.c中。__register_chrdev_region()函数执行流程图如下所示:

图2-4 __register_chrdev_region()函数执行流程图

chrdev_open()函数用于打开字符设备文件,其有两个参数,第一个是文件索引节点结构体指针,第二个参数是文件对象结构体指针。chrdev_open()函数执行流程图如下:

图2-5 chrdev_open()函数执行流程图

unregister_chrdev_region()函数用于注销设备号,其调用__unregister_chrdev_region()函数具体,代码在文件fs/char_dev.c中,如下所示:

void unregister_chrdev_region(dev_t from, unsigned count)

{

dev_t to = from + count;

dev_t n, next;

for (n = from; n < to; n = next) {

next = MKDEV(MAJOR(n)+1, 0);

if (next > to)

next = to;

kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

}

}

unregister_chrdev()函数也是用于注销一个设备号,与设备号注册函数register_chrdev()对应,其是对__unregister_chrdev()函数的封装,__unregister_chrdev()函数中调用了__unregister_chrdev_region()。__unregister_chrdev()函数代码在文件fs/char_dev.c中,如下所示:

void __unregister_chrdev(unsigned int major, unsigned int baseminor,

unsigned int count, const char *name)

{

struct char_device_struct *cd;

cd = __unregister_chrdev_region(major, baseminor, count);

if (cd && cd->cdev)

cdev_del(cd->cdev);

kfree(cd);

}

__unregister_chrdev_region()是注销设备号的核心函数,代码在文件fs/char_dev.c中。其执行流程比较简单,先从chrdevs散列表里中找到需要释放的char_device_struct结构实例,再将char_device_struct结构实例从chrdevs散列表中删除。返回搜狐,查看更多

责任编辑:

linux注册函数机制,Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动...相关推荐

  1. linux 对象管理器,Linux多安全策略和动态安全策略框架模块详细分析之函数实现机制中文件对象管理器分析(3)...

    3.决策的实施 当主体对客体进行访问时,客体管理器会收集主体和客体的SID,并根据此SID对在AVC中进行查找:如果找到,则根据相应的安全决策进行处理:反之客体管理器会将主体的SID.客体的SID以及 ...

  2. Linux字符设备驱动剖析

    以下内容转载于博客http://blog.csdn.net/yueqian_scut/article/details/45938557.有删改和格式调整,如有侵权,请告知删除 . 一.应用层的程序 很 ...

  3. <Linux开发>--驱动开发-- 字符设备驱动(3) 过程详细记录

    <Linux开发>–驱动开发-- 字符设备驱动(3) 过程详细记录 驱动开发是建立再系统之上的,前面作者也记录了系统移植的过程记录,如果有兴趣,可进入博主的主页查看相关文章,这里就不添加链 ...

  4. 字符设备驱动注册时资源浪费问题

    目录 1.原因 2.对策 2.1注册流程及API 2.2注销流程及API 3.字符设备驱动分步注册\注销实例 字符设备驱动内部实现原理https://blog.csdn.net/liu31929396 ...

  5. linux源码acl,Linux自主访问控制机制模块详细分析之posix_acl.c核心代码注释与acl.c文件介绍...

    原标题:Linux自主访问控制机制模块详细分析之posix_acl.c核心代码注释与acl.c文件介绍 2.4.4.6 核心代码注释 1 posix_acl_permission() int(stru ...

  6. linux open函数_Linux驱动开发 / 字符设备驱动内幕 (1)

    哈喽,我是老吴,继续记录我的学习心得. 一.保持专注的几个技巧 将最重要的事放在早上做. 待在无干扰环境下,比如图书馆. 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象. 让"开 ...

  7. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  8. 树莓派linux led字符设备驱动(信号量)

    一.信号量   Linux 内核提供了信号量机制,信号量常常用于控制对共享资源的访问.相比于自旋锁,信号量可以使线程进入休眠状态,使用信号量会提高处理器的使用效率.但是,信号量的开销要比自旋锁大,因为 ...

  9. Linux系统aboutyou,Linux字符设备驱动高级

    设备号:主设备号 + 次设备号   也可以叫主次设备号 新接口注册字符设备驱动,其实就是填充这个struct cdev类型的结构体,主要填充的内容就是 file_operations这个结构体变量,让 ...

最新文章

  1. 网关 0.0.0.0_久违的升级——全新米家智能多模网关
  2. BZOJ1449[JSOI2009]球队收益BZOJ2895球队预算——最小费用最大流
  3. 窄带信号和宽带信号的区别和联系
  4. C++ string字符串比较问题
  5. C#生成Excel报表 用MyXls组件生成更完美
  6. 【.NET特供-第三季】ASP.NET MVC系列:传统WebForm站点和MVC站点执行机制对照
  7. [Beego] 内置的模板函数(不同格式的字符串和html的互转)
  8. react 文本框_React自动完成文本框
  9. C++学习之路 | PTA乙级—— 1023 组个最小数 (20分)(精简)
  10. ora-03113 访问某条记录_用了Excel十几年,你居然不知道“记录单”?!可能错过一个亿……...
  11. 如何让火狐浏览器兼容window.event
  12. 怎么在表格中转换html格式,图解Excel与Html格式之间的互相转换
  13. 「深度」千篇一律的智能音箱,为何它们就是对显示屏“不感冒”?
  14. php开源路由器,路由 - Symfony开源 - Symfony中国
  15. mysql delphi_delphi 7 连接 MySql
  16. 从技术和历史的视角,理解Too many technical terms in FE BE
  17. NIST计划对量子加密进行众测
  18. 【论文学习之SNE-RoadSeg】跑通SNE-RoadSeg代码
  19. 如何通过局域网共享本机网页
  20. 第三方支付交易简单流程

热门文章

  1. ubuntu编译android4.0
  2. mysql设置索引树长度_MySQL索引-B+树
  3. java中pack函数_java - Java函数pack(),JFrame大小 - 堆栈内存溢出
  4. numa节点_鲲鹏性能优化十板斧之前言 | 鲲鹏处理器NUMA简介与性能调优五步法
  5. 几种排序算法性能的比较
  6. 探秘!在阿里云做产品经理是怎样的体验?
  7. 10倍性能提升!DLA SQL推出基于Alluxio的数据湖分析加速功能
  8. 看完这一篇,再也不用担心 Git 的“黑魔法”
  9. 朱峰谈概念设计(五):进入焦距
  10. Galgame研发日志:独立游戏制作前应当进行的第一步