十天学Linux内核之第九天---向内核添加代码

原文:十天学Linux内核之第九天---向内核添加代码

  睡了个好觉,很晚才起,好久没有这么舒服过了,今天的任务不重,所以压力不大,呵呵,现在的天气真的好冷,不过实验室有空调,我还是喜欢待在这里,有一种不一样的感觉,在写了这么多天之后,自己有些不懂的页渐渐的豁然开朗了吗,而且也交到了一些朋友,真是相当开心啊。今天将介绍一下向内核中添加代码,一起来看看吧~

  先来熟悉一下文件系统,通过/dev可以访问Linux的设备,我们以men设备驱动程序为例来看看随机数是如何产生的,源代码在dirvers/char/mem.c上可以查看

static int memory_open(struct inode * inode * inode,struct file * filp)
{
    switch (iminor(inode)) {  //switch语句根据从设备号来初始化驱动程序的数据结构
        case 1:
    ...
        case 8:
            filp->f_op = &random_fops;
            break;
        case 9:
            filp->f_op = &urandom_fops;
            break;

  那么上述程序的filps和fop是什么呢?实际上filp只是一个文件结构指针,而fop是一个file_operations结构指针,内核通过file_operations结构来确定操作文件时要调用的函数,下面的file_operations结构用于随机设备驱动的部分内容,代码在include/linux/fs.h上可以查看到:

struct file {
    struct list_head f_list;
    struct dentry *f_dentry;
    struct vfsmount *f_vfsmnt;
    struct file_operations *f_op;
    atomic_t f_count;
    unsigned int f_flags;
...
    struct address_space *f_mapping;
};

  q驱动程序所实现的函数必须符合file_operations结构中所列出的函数原型,代码在dirvers/char/random.c上可以查看:

struct file_operations random_fops = {
    .read = random_read,
    .write = random_write,
    .poll = random_poll,  //poll操作允许某种操作之前查看该操作是否阻塞
    .ioctl = random_ioctl,
};  //随机设备提供的操作有以上

struct file_operations urandom_fops = {
    .read = random_read,
    .write = random_write,
    .ioctl = random_ioctl,
}; //urandom设备提供的操作有以上

  如果设备驱动程序在内核空间运行,但是缓冲区却位于用户空间,那我们该如何才能安全访问buf中的数据呢,下面来说下数据在用户空间和内核空间之间的奥秘,Linux提供的copy_to_user()和copy_from_user()使得驱动程序可以在内核空间和用户空间上传递数据,在read_random()中,通过extract_entropy()函数来实现这个功能,下面代码在dirvers/char/random.c上可以查看(下面的代码没有敲完,主要是不是很懂,望大神指教)

static ssize_t extract_entropy(struct entract_syore *r,void *buf,size_t nbytes,int flags)
{
...
{
    static ssize_t extract_entropy(struct entropy_store *r,void *buf,size_t nbytes,int flags)
{
...

  内核空间和用户空间的程序可能都需要使用已经获得的随机数,内核空间的程序可以通过不设置标志位来避免函数copyto_user()带来的额外开销。除了通过设备驱动程序向内核添加代码之外,还有别的方式 的,用户空间可以通过系统调用来访问内核服务程序和系统硬件,这里不多阐释,都知道有这回事就行了。

 

  下面我们来介绍怎么去编写源代码,当我们去编写一个复杂的设备驱动程序时,也许要输出驱动程序中定义的某些符合,以便让内核其它模块使用,这些通常被用在低级的驱动程序中,以便根据这些基本的函数来构建更高级的驱动程序,在Linux2.6内核中,code monkey可以用如下两个宏输出符号,代码在include/linux/module.h中查看:

#define EXPORT_SYMBOL(sym)
    __EXPORT_SYMBOL(sym, "")

#define EXPORT_SYMBOL_GPL(sym)
    __EXPORT_SYMBOL(sym, "_gpl")

  目前为止,我们介绍的设备 驱动程序都是主动操作,或者对设备的数据进行读写操作,那么它的功能不止这些的时候会怎么样呢?在Linux中,设备驱动程序解决这些问题的典型方式就是使用ioctl。ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

调用个数如下:

int ioctl(int fd, ind cmd, …);

其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

 

ioctl命令号:

dir:

  代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。

type:

描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。

  nr:

ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。

  size:

ioctl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。

 

ioctl返回值:

   ioctl函数的返回值是一个整数类型的值,如果命令执行成功,ioctl返回零,如果出现错误,ioctl函数应该返回一个负值。这个负值会作为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参考<linux/errno.h>和<asm/errno.h>头文件。

 

ioctl参数:

  首先要说明这个参数是有用户空间的程序传递过来的,因此这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是无法直接使用它的。为了解决在内核空间使用用户空间地址的数据,Linux内核提供了以下函数,它们用于在内核空间访问用户空间的数据,定义在<asm/uaccess.h>头文件中:

unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
unsigned long __must_check copy_from_user(void *to,
const void __user *from, unsigned long n);

copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

#define get_user(x,ptr)
#define put_user(x,ptr)//x是内核空间的简单数据类型地址,ptr是用户空间地址指针。

  

cmd参数如何得出:

  一个cmd参数被分为4段,每段都有其特殊的含义,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。解释一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt这两个文档有说明的 。

1)幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有一个文档给出一些推荐的或者已经被使用的幻数

2)序数:用这个数来给自己的命令编号,占8bit(_IOC_NRBITS),我的程序从1开始排序。

 

3)数据传输方向:占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。

  • _IOC_NONE:值为0,无数据传输。
  • _IOC_READ:值为1,从设备驱动读取数据。
  • _IOC_WRITE:值为2,往设备驱动写入数据。
  • _IOC_READ|_IOC_WRITE:双向数据传输。

4)数据大小:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。

 

 ioctl如何实现:

  在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说,关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

  命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情

所以在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型 | 序列号 | 方向 |数据尺寸|

|----------|--------|------|--------|

| 8 bit    |  8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

  这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

 

  在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。这里有必要再一次强调的是,在内核模块或驱动程序的编写中,我们强烈建议你使用内核提供的接口来生成并操作ioctl命令号,这样可以对命令号赋予特定的含义,使我们的程序更加的健壮;另一方面也可以提高程序的可移植性。

 

  最后我们来介绍一下添加代码后的编译和调试,在内核中添加代码后就需要不断运行,修复错误,我们知道当对/proc文件系统进行读写操作时,它的每一个结点都链接到一个内核函数,在Linux2.6内核中,要想你的设备能够被访问,首先就要在/proc文件系统中创建一个入口,这个可以通过creat_proc_read_entry()来实现,代码在include/linux/proc_fs.h上查看:

static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
    mode_t mode,struct proc_dir_entry *base,
    read_proc_t *read_proc,void * data)

*name是结点在/proc文件系统的入口,*base指向设置proc文件的目标路径,如果它的值为NULL,表示该文件就在/proc目录下,读取该文件可以调用*read_proc指向的函数。这里也不多加阐释了,整个也是很简单的过程。

  小结

  今天的重点是iotcl函数了,其中还有很多向内核中添加代码的细节没有讲到,主要是这些都涉及到过多的操作,需要大家多看源代码并且多动手在Linux上操作才能完全掌握,,今天写的一些也借鉴了一些大牛的文章,总之 收获很多,最后几天了,真的是很开心啦,和大家一起分享真的很快乐的~~

 

  版权所有,转载请注明转载地址:http://www.cnblogs.com/lihuidashen/p/4255826.html

时间: 2024-09-10 06:05:33

十天学Linux内核之第九天---向内核添加代码的相关文章

十天学Linux内核之第六天---调度和内核同步

原文:十天学Linux内核之第六天---调度和内核同步 心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明年的全国大赛,说起来我们学校历史上也就又一次拿国一的,去了一次人民大会堂领奖,可以说老大是对我们寄予厚望,以后我会专攻仪器仪表类的题目,激情不灭,梦想不息,不过最近一段时间还是会继续更新Linux内核,总之,继续加油~ Linux2.6版本中的

十天学Linux内核之第一天---内核探索工具类

原文:十天学Linux内核之第一天---内核探索工具类 寒假闲下来了,可以尽情的做自己喜欢的事情,专心待在实验室里燥起来了,因为大二的时候接触过Linux,只是关于内核方面确实是不好懂,所以十天的时间里还是希望能够补充一下Linux内核相关知识,接下来继续待在实验室里想总结一下Linux内核编程,十天肯定完全掌握不了Linux内核,这里我也只是把自己认为不是很好懂并且很重要的难点疑点写出来,和大家一起分享,希望大家改正互相学习. Linux的具体概述这里就不多说了,今天主要讲的是Linux内核中

十天学Linux内核之第四天---如何处理输入输出操作

原文:十天学Linux内核之第四天---如何处理输入输出操作 真的是悲喜交加呀,本来这个寒假早上8点都去练车,两个小时之后再来实验室陪伴Linux内核,但是今天教练说没名额考试了,好纠结,不过想想就可以睡懒觉了,哈哈,自从大三寒假以来还没睡过懒觉呢,现在也有更多的时间来分享自己学习Linux内核的感受,前几天觉得就是自己也有些不懂的,相信大家看了也是很模糊,以后我会标志出来自己不懂的,希望大神们指教,也希望大家多多指点,共同攻克Linux内核,今天将讲到处理器是如何与其它设备进行交互的,内核又是

十天学Linux内核之第二天---进程

原文:十天学Linux内核之第二天---进程 都说这个主题不错,连我自己都觉得有点过大了,不过我想我还是得坚持下去,努力在有限的时间里学习到Linux内核的奥秘,也希望大家多指点,让我更有进步.今天讲的全是进程,这点在大二的时候就困惑了我,结果那个时候我就止步不前了,这里主要讲的是为何引入进程.进程在Linux空间是如何实现的,并且描述了所有与进程执行相关的数据结构,最后还会讲到异常和中断等异步执行流程,它们是如何和Linux内核进行交互的,下面我就来具体介绍一下进程的奥妙. 首先我们要明确一个

十天学Linux内核之第十天---总结篇(kconfig和Makefile &amp; 讲不出再见)

原文:十天学Linux内核之第十天---总结篇(kconfig和Makefile & 讲不出再见) 非常开心能够和大家一起分享这些,让我受益匪浅,感激之情也溢于言表,,code monkey的话少,没办法煽情了,,,,,,,冬天的风,吹得伤怀,倒叙往事,褪成空白~学校的人越来越少了,就像那年我们小年之后再回家的场景一样,到处荒芜,然而我们的激情却不褪去,依然狂躁在实验室凌晨两点半的星空里,也许今天又会是这样的一年,不一样的是身边的人变成学弟学妹了,而我们几个大三老家伙依然在,为自己喜欢的事情,为

十天学Linux内核之第七天---电源开和关时都发生了什么

原文:十天学Linux内核之第七天---电源开和关时都发生了什么 说实话感觉自己快写不下去了,其一是有些勉强跟不上来,其二是感觉自己越写越差,刚开始可能是新鲜感以及很多读者的鼓励,现在就是想快点完成自己制定的任务,不过总有几个读者给自己鼓励,很欣慰的事情,不多感慨了,加紧时间多多去探索吧,今天要去描述的是电源开和关时都发生了什么,一起去看看吧~~ bootloader引导装入程序将内核映像加载到内存并处理控制权传送到内核后在内核引导时每个子系统都必须要初始化,我们根据实际执行的线性顺序跟踪内核的

十天学Linux内核之第五天---有关Linux文件系统实现的问题

原文:十天学Linux内核之第五天---有关Linux文件系统实现的问题 有时间睡懒觉了,却还是五点多醒了,不过一直躺倒九点多才算起来,昨晚一直在弄飞凌的嵌入式开发板,有些问题没解决,自己电脑系统的问题,虽然Win10发布了,,但我还是好喜欢XP呀,好想回家用用家里的XP来玩玩这块板子,不知不觉也第五天了,感觉代码都有些模糊,连自己都不是很清楚了,担心现在分享起来比较困惑,各路大神多加批评呀,觉得渣渣的尽量指正出来,拉出来批评,今天还是来总结一下有关Linux文件系统的问题吧~ Linux的使用

十天学Linux内核之第八天---构建Linux内核

原文:十天学Linux内核之第八天---构建Linux内核 今天是腊八节,说好的女票要给我做的腊八粥就这样泡汤了,好伤心,好心酸呀,看来代码写久了真的是惹人烦滴,所以告诫各位技术男敲醒警钟,不要想我看齐,不然就只能和代码为伴了的~~话说没了腊八粥但还是有代码,还有各位读者的支持呀,所以得继续写下去,静下心来,完成Linux内核的学习,坚持,加油~ 到目前为止,我们已经认识了Linux内核子系统,也探究了系统的初始化过程,并且深入探索了start_kernel()函数,同样,了解内核映像的创建也是

十天学Linux内核之第三天---内存管理方式

原文:十天学Linux内核之第三天---内存管理方式 昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今天将会讲诉Linux如何追踪和管理用户空间进程的可用内存和内核的可用内存,还会讲到内核对内存分类的方式以及如何决定分配和释放内存,内存管理是应用程序通过软硬件协助来访问内存的一种方式,这里我们主要是介绍操作系统正常运行对内存的管理.插个话题,刚才和姐姐聊天,她快结婚了,说起了自己的初恋,可能是一句很