24小时学通Linux内核之向内核添加代码

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 1个月
简介:   睡了个好觉,很晚才起,好久没有这么舒服过了,今天的任务不重,所以压力不大,呵呵,现在的天气真的好冷,不过实验室有空调,我还是喜欢待在这里,有一种不一样的感觉,在写了这么多天之后,自己有些不懂的页渐渐的豁然开朗了吗,而且也交到了一些朋友,真是相当开心啊。

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

  先来熟悉一下文件系统,通过/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

相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
122 0
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
237 67
|
3月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
102 0
|
5月前
|
存储 Linux
Linux内核中的current机制解析
总的来说,current机制是Linux内核中进程管理的基础,它通过获取当前进程的task_struct结构的地址,可以方便地获取和修改进程的信息。这个机制在内核中的使用非常广泛,对于理解Linux内核的工作原理有着重要的意义。
211 11
|
6月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
251 16
|
8月前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
447 15
|
8月前
|
安全 Linux 测试技术
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
196 4
|
10月前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
195 26
|
10月前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
390 24
|
9月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。