0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从微观角度来看Linux内核设计

Linux阅码场 来源:未知 作者:李倩 2018-11-15 16:29 次阅读

◆◆从微观角度来看Linux内核设计◆◆

余生皆欢喜

最近总结出来学习内核有两个大的角度,一种就是从宏观角度来看,总的来说就是顺着抽象,管理,操作来看,这种角度更多的是内核中应用层面的内容,用来理解内核中是怎么运转起来的。第二种就是从内核的最细节部分出发,深入到一个个具体的宏,看看内核设计者在细节部分有着怎么样的巧妙之处,这样也有助于我们夯实C语言基础,也可以学习到GNU C的用法。

最近学习了如下的GNU C的内容:

指定初始化

语句表达式

typeof关键字

内核第一宏

我们来看看这些内容是怎么设计的,GNU C就是打辅助的,专门为了OS而存在,(为什么全世界不统一使用GNU C呢?)它带来了太多的方便,换句话说,它帮助内核设计人员解决了很多内核设计者在设计内核时所遇到的问题,我这样认为,GNU C中每一条功能,就是内核设计者在实际设计中遇到的问题。

这里再次分析总结gitbook中的两个宏,一个是max/min宏,一个是内核第一宏container_of。

max/min宏

内核中的样子:

这里的max宏可以让我们学会语句表达式,typeof关键字;基础方面可以巩固运算符优先级。

这个宏是怎么得到的呢?

我们来写一个宏,用来比较两个变量的大小,我一定会这么写:

那么我们来比较一下4!=4和2!=3,结果是错误的,原因是运算符优先级出了问题。那么我们来解决,使用括号是最简单的方法:

我们来运行一条语句:printf("max = %d\n",3 + MAX(4,5));,结果是7,这里是因为+的运算优先级大于>了,换句话说,是因为外部的语句,影响到了宏,那么我就把自己隔离起来:

再来运行一下printf("max=%d\n",MAX(2++,3++));,输出的会是4,但我们只想要比较2和3的值,这里是因为自增自减运算符导致的问题,那么怎么解决呢?和交换两个数字的想法一样,通过一个中转值来存放,就可以隔离影响了

这里就有一些内核代码中的味道了,注意一个细节,这里的第四行没有括号了,为什么?这里就是因为语句表达式了,不存在上面的影响了。这里我们回顾一下代码,再看看目前这个宏的第二三行,是int,也就是我们这个宏只能比较int类型的变量,而在内核中需要比较大小的变量有很多,那么我们来提高一下:

这个宏就可以用来比较任意类型的变量了,再来看一下代码,我们需要替换的变量有type,x,y三个,如果有了typeof关键字,我们还可以减少一个:

接着来,如果我们使用了一次宏,是MAX(i,j),其中i是int类型,j是float类型,这样比较是可以的,但是在内核的设计过程之中,很有可能有些地方会出现问题,所以还需要改造:

这就是究极形态了,我们添加了第四行的代码,来看&_min1,它的意思是取_min1的地址,而&_min2的意思是取_min2的地址,我们也知道,这两个地址肯定不可能是一样的,那为什么还要这样写呢?这里就很巧妙了,当两个变量的类型不同时,对应的地址,也就是指针类型也不相同,比如一个是int类型,一个是char类型,那么指向他们的指针就是int *和char *,这两个指针在比较的时候,就比较的是类型了。如果比较的类型不一样,gcc会警告的。

我们来看这一系列改进,我相信内核设计人员也想把代码写成# define MAX(x,y) x > y? x : y的样子,但是现实是残酷的,我们为了代码的健壮性,就必须这样一步一步来改进,所以,内核代码看起来很复杂,又很巧妙,是因为我们直接看到的是究极形态的代码,它是向现实妥协了多次以后的产物,也就是健壮性+GNU C。但是,内核设计者的初衷,或者说最初的想法和我们都是一样的。

有内核源码在旁边,巩固基础知识就不用像以前的学习模式了,可以在源码中代入学习,增添一份趣味性,并且可以很快理解。

在以后处理因为运算符而导致的问题的时候,使用括号是最方便的,内核就这么干了。

在写程序的时候,要巧用中转变量,虽然只是简单的存入另一个变量之中,但是代码的健壮性提高了很多。

两个地址在进行比较的时候,我们可以得知这两个指针类型是否一致。

内核第一宏

gitchat中把container_of宏叫做内核第一宏,我也很喜欢这个称号,因为学内核两个月里见这个宏的次数太多了。在陈老师讲list.h的时候,就学习过这个宏,但是并没有完完全全地剖析开。

高能预警:

这个宏的作用我们已经很清楚了,根据结构体中某一成员的地址,就可以获得这个结构体的首地址,再说的明白一点,假如你是内核设计人员,前面也说道了,我们已经对数据进行了多次封装,我们一定会遇到这种情况:传给某个函数的参数是某个结构体成员变量,但是我们在这个函数中还想使用这个结构体的其它成员变量,这个时候就需要想办法,于是才有了我们现在看到的这个内核第一宏。

它的三个参数是:

ptr:此结构体内成员member的地址

type:此结构体类型

member:此结构体内的成员

我们直接看代码,这个宏的最后的值,就是最后一条语句,(type *)( (char *)__mptr - offsetof(type,member) );}),这条语句也是这个宏的中心思想拿结构体成员的地址减去此成员的偏移,这里也体现了指针做减法是很有意义的。成员的地址好说,我们直接传进来了,偏移是通过offsetof来实现的,来看看这个offsetof:将0强制类型转换成这个结构体的指针类型,然后访问这个成员,加上&得到它的偏移,返回。这里要注意一下,那就是为什么只通过TYPE和MEMBER就可以得到偏移,我一开始认为的是内核中这个类型的结构体多了,到底用的是哪一个结构体来得到的,最后发现,并没有关系,因为我们需要的是字节数,与实际这个字段赋什么样的值并没有关系,因为所有这个类型的结构体中,各成员的字节大小是一样的。

再来看(char *)__mptr,这个通过第四行代码可以很容易得出它是成员的地址,为什么要强制转换成char *呢?转换成int *不行吗?这里又可以学习一下C指针的基础知识,通过代码可以很容易知道有什么区别:

打印出来的值,p(int *)类型,增加了4字节,而q(char *)增加了1字节,回到宏中,我们的偏移是按照字节来算的,所以不能使用(int *),必须使用(char *)。在最后,再次强制类型转换成指向这个结构体的指针类型。

回过头来看第四行代码,const typeof( ((type *)0)->member ) *__mptr = (ptr);,这里和max宏之中类似,使用了中转变量来存放,这里为什么要使用中转变量?max宏中是为了防止自增自减的影响(当然只是原因之一了),但我们在使用的时候总不至于发过来成员的地址再加一个++运算符吧。我们可以从const的用法来思考,const int * p //p可变,p指向的内容不可变,所以,使用了const,我们就可以保证ptr指向的内容在这里只是可读的,这也许就是为什么使用中转变量的原因,为了防止我们通过指针改变了原有的成员的值,毕竟指针虽然强大,但也是很危险的,所以,这里的中转要配合const来使用。既然是中转,那么类型就必须要求一致了,所以我们要得到和这个成员一致的类型,就通过typeof来得到了,将0强制类型转换成这个这个结构体的指针类型,然后访问这个变量,(注意仔细看代码,这里的代码和offsetof非常类似)这里没有使用&,所以只是访问到变量了,没有得到偏移。另外根据const的用法,第四行的代码也可以写成typeof( ((type *)0)->member ) const *__mptr = (ptr);也就是把const放到后面。

我们再来注意一个细节,就是offsetof里的size_t,这个是什么,这里在敲代码的过程中偶然学到一个小技巧,就是这个size_t绝对是封装,就是C语言中那几种变量类型,我们可以typedef int size_t;然后运行,gcc就会报错,并且会给你显示:以前已经定义过:typedef __SIZE_TYPE__ size_t,并且会指定这个值在哪个文件,我们就可以知道它的真面目了。换句话说,gcc这么强大,我们当然可以把它当做一个学习工具来使用。

另外还可以通过sublime,可以很快找到它的真面目(3.10版本):

最后,为了更深入理解这些知识的使用方法,还是需要自己动手来敲代码的,尤其是内核第一宏,将代码写到用户态下,然后疯狂改造,这样才会真正理解这个宏。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11225

    浏览量

    208906
  • C语言
    +关注

    关注

    180

    文章

    7598

    浏览量

    136162
  • 代码
    +关注

    关注

    30

    文章

    4744

    浏览量

    68344

原文标题:赵晨雨: 从微观角度来看linux内核设计

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内核data段和bss段的区别

    进程的角度Linux内核是采用虚拟地址空间的,如下两张图所示,分别为32位、64位系统下进程地址空间的大概布局。
    发表于 10-13 17:07 8846次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>data段和bss段的区别

    Linux内核中系统调用详解

    Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。某种角度来看,系
    发表于 08-23 10:37 763次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>中系统调用详解

    Linux内核教程

    本章学习目标掌握LINUX内核版本的含义理解并掌握进程的概念掌握管道的概念及实现了解内核的数据结构了解LINUX内核的算法掌握
    发表于 04-10 16:59 0次下载

    linux内核启动流程

    Linux的启动代码真的挺大,汇编到C,Makefile到LDS文件,需要理解的东西很多。毕竟Linux内核是由很多人,花费了巨大的时间
    发表于 11-14 16:19 4339次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b>启动流程

    Linux 内核才是真正的规则改变者

    Linux 内核被称为创新,但它又被称为现代计算中最大的奇迹,一个微观世界中的庞然大物。
    发表于 09-28 01:31 721次阅读

    Linux内核架构--基本概念

    首先,Linux整体的架构如图: 再来看Linux内核架构, 内核由五个主要子系统组成: Process Scheduler : 进程调度(
    发表于 05-20 09:28 725次阅读

    linux内核是什么_linux内核学习路线

    Linux内核是一个操作系统(OS)内核,本质上定义为类Unix。它用于不同的操作系统,主要是以不同的Linux发行版的形式。Linux
    发表于 09-16 15:49 2613次阅读

    linux内核参数设置_linux内核的功能有哪些

    本文主要阐述了linux内核参数设置及linux内核的功能。
    发表于 09-17 14:40 1362次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b>参数设置_<b class='flag-5'>linux</b><b class='flag-5'>内核</b>的功能有哪些

    最硬核的Linux内核文章

    来源 :头条号@Linux学习教程,冰凌块儿 01 前言 本文主要讲解什么是Linux内核,以及通过多张图片展示Linux内核的作用与功能,
    的头像 发表于 10-19 17:46 2096次阅读
    最硬核的<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>文章

    快速理解什么是Linux内核以及Linux内核的内容

    01 前言 本文主要讲解什么是Linux内核,以及通过多张图片展示Linux内核的作用与功能,以便于读者能快速理解什么是Linux
    的头像 发表于 10-21 12:02 4257次阅读
    快速理解什么是<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>以及<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>的内容

    如何使用Linux内核实现USB驱动程序框架

    Linux内核提供了完整的USB驱动程序框架。USB总线采用树形结构,在一条总线上只能有唯一的主机设备。 Linux内核主机和设备两个
    发表于 11-06 17:59 20次下载
    如何使用<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>实现USB驱动程序框架

    Linux环境编程:应用到内核

    Linux环境编程:应用到内核资料下载。
    发表于 06-01 14:51 18次下载

    软件角度分析linux内核USB子系统的热插拔过程

    本文软件角度分析linux内核USB子系统的热插拔过程,以实际分析思路和过程行文,基于linux内核
    的头像 发表于 01-15 09:28 5418次阅读

    linux内核源代码详解

     在安装好的Linux系统中,内核的源代码位于/ust/src/linux.如果是GNU网站下载的Linux
    发表于 09-06 17:01 4次下载

    C++在Linux内核开发中争议到成熟

    Linux 内核邮件列表中一篇已有六年历史的老帖近日再次引发激烈讨论 —— 主题是建议将 Linux 内核的开发语言 C 转换为更现代的
    的头像 发表于 01-31 14:11 586次阅读
    C++在<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>开发中<b class='flag-5'>从</b>争议到成熟