最近一头扎进了 Linux 内核的学习中,对于我这样一个没什么 C 语言基础的新生代 Java 农民工来说实在太痛苦了。Linux 内核的学习,需要的基础知识太多太多了:C 语言、汇编语言、数据结构与算法、操作系统原理、计算机组成原理、计算机体系结构。在囫囵吞枣补完一些计算机基础知识后,还是在一开始就被一个小小的 offsetof 宏搞晕了。

offsetof 宏

先来看看offsetof宏是什么,这是定义在 <linux/stddef.h>中的一个宏,用来计算一个 struct 结构体中某个成员相对于结构体首地址的偏移量。这是一个很有用的宏,因为 Linux 内核的数据结构大量用了嵌入式的结构体(什么是嵌入式结构体,可以参考 <linux/list.h> 的巧妙设计,这个以后再讲)。

// offsetof 宏的定义
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

当看到这个东西完全傻眼了,size_t 是啥东东,((TYPE *)0) 又是啥东东,这个 0 又是什么鬼?特别看到后面是访问一个成员,我去,这不是 Java Farmer 眼中的 NPE 吗?因为这个宏展开后没见任何一个结构体的实例。-_-! 于是上网搜索一番。

size_t 基本知道了就是代表一个整数类型,只是为了程序的可移植、效率等原因定义成这样,具体解释可以看《为什么size_t重要?》这篇文章。

至于 &((TYPE *)0)->MEMBER) 这段代码,简单来说就是取 TYPE 类型的结构体里名字为 MEMBER 的成员的地址,是相对 0 的地址(0 就是 TYPE 结构体的首地址)。C 语言里指针就是个无符号整数,所有 0 也可以转成一个 TYPE 类型的指针,那么不写 0 行吗?答案是肯定的,但算偏移量需要后面再减去首地址值,例如((size_t) (&((TYPE *)1000)->MEMBER)-1000),这样也行,但是,这就有点多此一举了。

另外,很重要的一点:这样算偏移地址仅仅是从逻辑计算上来写计算的表达式,实际上程序运行时是不会发生任何计算,而是编译器直接就能取到这个地址偏移量,因而也不会有任何的访存操作。下面从一个例子可以证明:

1、先写个 C 测试程序

#include <stdio.h>

// 定义一个取偏移量的宏
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) // 定义一个结构体
struct my_struct {
int a,b,c;
struct my_struct *next; // 后面我要算这个成员的偏移量
}; // main 函数很简单就是输出偏移量的值
void main() {
printf("offsetof next=%d\n", offsetof(struct my_struct, next));
}

编译,运行,最后输出的结果是:offsetof next=16,为什么是16?next 前面有三个 int 类型的成员,各占 4 字节,那 next 应该是从 12 开始,其实这要看编译的是 64 位还是 32 位,因为笔者的机器是 64 位的 Redhat,而 gcc 编译选项没加 -m32,所以编译出来的程序自然是 64 位的了,因此 next 指针是 8 个字节,要 8 字节对齐的话,自然不能从 12 开始,要从 16 开始。整个结构体的长度是 24 字节(即 sizeof(struct my_struct) = 24)。

2、第二部再将上面的 C 代码编译成汇编看看,指令是怎么执行的

/**
* 以 . 开头的行我们不用管它,都是些编译器生成的东西,只看汇编指令即可
**/
.file "mymain.c"
.section .rodata
.LC0:
.string "offsetof next=%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $16, %esi /* printf 的第二个参数,看,这里没有任何计算,编译时就知道偏移量是 16,直接存到 esi 寄存器作为 printf 函数的实参 */
movl $.LC0, %edi /* printf 的第一个参数,就是上面的字符串常量 */
movl $0, %eax
call printf /* 调用 printf 函数,要说明的是,在 x86-64 结构体系中,有 6 个寄存器是可以用于传参的(这里用了 esi 和 edi),多于 6 的其余就压栈,也就是上面 rsp 所指的栈顶 */
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits

好,到这里,从上面汇编指令可以看到,offsetof 宏展开后就是一个 16 这个值,编译器直接就优化算好了,所有汇编指令仅仅是为了调用 printf 函数所做的压栈保护现场,传参,弹栈恢复现场这些指令。当然,上面说的是在 x86 结构体系下的指令结构。

小结

Linux 是一个非常庞大的系统,几乎涵盖了所有计算机基础知识。学习 Linux 内核是非常艰巨的,不但需要非常牢固的计算机基础,还需要想象力,大局观。学习了一个月,总结几点经验:

1、基础知识要时时温习,“温故知新”。每次看都会有不同的感悟和理解。像这篇学习笔记就是对基础知识的温故,从汇编指令角度看编译器对宏展开做的工作。永远不要相信网上一些什么视频教程说的不需要什么基础,学习 Linux 内核需要的基础知识太多了,而且这些视频也不必要浪费时间看,浪费钱买,都是一些二手知识;也永远不要相信什么“一文读懂xxx”这类的文章,同样是一些二手知识,是不是发现看这些文章很容易就忘了?掌握知识从来没有捷径。

基础、基础,基础才是最重要的,计算机技术发展了这么多年,以及近些年来火起来的什么大数据,AI,其实都不是什么新东西,本质还是那些计算机基础知识原理和数学

  基础知识脱节,没可能入门 Linux 内核,不要说入门,入窗户都不可能。所以想学 Linux 内核,从基础知识开始,无论基础有多差,只要肯下功夫,不成问题,这些基础知识包括:

  • 计算机组成原理:站在抽象的层次理解计算机的工作原理,CPU 如何取指执行(这个可以说是现代计算机工作的本质),内存如何工作,高速缓存如何工作,中断的原理,外设如何协同并行工作等等;

  • C 语言:这个不用说了,肯定最重要的,C 语言玩得溜,可以省大量时间;

  • 数据结构及算法:Linux 里可以说是各种数据结构和算法的大杂烩,你能想到的里面都有,同样这个玩得溜,可以省大量时间;

  • 汇编语言(计算机体系结构):汇编其实很简单,没什么好学的,这是要与某一个结构体系紧密结合(基本都 x86 最熟吧),不用强记(记也记不住),只要混个脸熟就好,需要用的时候查手册即可,主要是结构体系的原理,高速缓存、缓存一致,流水线原理;

  • 操作系统原理:理论指导实践,有了理论,才容易形成蓝图。而学习 Linux 内核只是实践。

2、大局观,抓主线,虽然 Linux 内核代码将近 800MB,其实大部分不怎么需要看。网上很多教程,其实都不怎么好,要么泛泛而谈,要么讲些过时的(很多将0.11版的内核,个人觉得没啥价值,纯属浪费时间),要么一下子就从某一结构体系讲起,初学者很容易被绕晕,还有些直接就从怎么自己写一个操作系统开始,我们要学的是 Linux 内核,一开始讲这些个人觉得没学会走路就学飞;不可否认,讲这些教程的人也许很牛,但个人认为不是一个好老师。所以:

  • 我们学 Linux 的目的是什么,不同的人有不同的需求,像 Java 过来的新生代农民工,应该着重学习 Linux 内核的设计哲学,例如 kernel 是如何能像我们 Java 面向对象一样,与各种结构体系(arch)完美适配的,设计的哲学,这些都是网上那些视频没讲的。再进一步就是细致到进程管理、内存管理、磁盘这些怎么管理,学会这些,那些老喜欢被问的什么 kafka 原理啊、零拷贝啊这些简直就是小菜。作为 Javaer,工作的环境就是 Linux 内核,因此,Linux 太重要了,能学多深就学多深。

  • 要多想象,根据上面的基础知识,想象,爱因斯坦也说过,想象力比知识跟重要。所以,我们在学习 Linux 内核时要多想象,猜测,带着问题去学,验证;

  • Linux 是一个巨复杂的系统,Javaer 更应该学习的是如何应对复杂系统的方法;

  • 上面三点个人才觉得是一个工程师最有价值的地方,这些工程师才是工匠。

3、多动手,搭建环境学习源码,多编写代码验证,特别是从 Java 转过来的。“纸上得来终觉浅,绝知此事要躬行”。

4、由于笔者也是刚刚才开始学 Linux 内核不久,水平有限,有不正确的地方多多交流,不胜感激。

Linux 内核预备知识:浅析 offsetof 宏以及新手的所思所想的更多相关文章

  1. (转)linux内核虚拟文件系统浅析

    转自http://hi.baidu.com/_kouu/item/4e9db87580328244ef1e53d0 ###### 虚拟文件系统(VFS)在我看来, "虚拟"二字主要 ...

  2. (转)linux内核虚拟文件系统浅析【转】

    转自:https://www.cnblogs.com/woainilsr/p/3590716.html 转自http://hi.baidu.com/_kouu/item/4e9db8758032824 ...

  3. Linux内核零碎知识

    UNIX系统:内核.shell外壳.文件系统.工具或应用程序. 操作系统功能:进程与处理机管理.存储管理.设备管理.作业管理.文件管理. 内存是磁盘的缓存,cache是内存的缓存. 可把内核看作是不断 ...

  4. C语言:类似linux内核的分等级DEBUG宏(打印宏)

    总结几种log打印printf函数的宏定义 http://blog.chinaunix.net/uid-20564848-id-73402.html #include <stdio.h> ...

  5. linux内核自锁旋spinlock常用宏解释

    转自:http://blog.sina.com.cn/s/blog_6929134b0100tdn8.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持, ...

  6. [Linux] 001 预备知识

    Unix 1965年 MIT,通用电气(GE),AT&T 的贝尔实验室联合开发 项目名称:Multics 目标:开发一种交互式的,具有多道程序处理能力的分时操作系统 后来:贝尔实验室宣布退出 ...

  7. Linux内核驱动基础(一)常用宏定义【转】

    转自:http://blog.csdn.net/tommy_wxie/article/details/9427081 一: __init和__initdata  : __exit和__exitdata ...

  8. 【转】linux内核中writesb(), writesw(), writesl() 宏函数

    writesb(), writesw(), writesl() 宏函数 功能 : writesb()    I/O 上写入 8 位数据流数据 (1字节) writesw()   I/O  上写入 16 ...

  9. [Linux] 002 预备知识

    1. 开源软件 (1) 常见开源软件 Apache NGINXTM MySQL PHP Saamba mongoDB Python Ruby Sphinx -- (2) 开源软件的特点 绝大多数开源软 ...

随机推荐

  1. Docker:docker部署PXC-5.7.21(mysql5.7.21)集群搭建负载均衡实现双机热部署方案

    单节点数据库弊端 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 推荐Mysql集群部署方案 PXC (Percon ...

  2. 被swoole坑哭的PHP程序员 (转)

    本文主要记录一下学习swoole的过程.填过的坑以及swoole究竟有多么强大! 首先说一下对swoole的理解:披着PHP外衣的C程序.很多PHPer朋友看到swoole提供的强大功能.外界对其的崇 ...

  3. 《PHP扩展学习系列》系列分享专栏

    <PHP扩展学习系列>系列分享专栏   <PHP扩展学习系列>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/20177 ...

  4. Java 设置PDF跨页表格重复显示表头行

    在创建表格时,如果表格内容出现跨页显示的时候,默认情况下该表格的表头不会在下一页显示,在阅读体验上不是很好.下面分享一个方法如何在表格跨页是显示表格的表头内容,这里只需要简单使用方法 grid.set ...

  5. 第九章 身体质量指数BMI的python实现

    身体质量指数BMI:对身体质量的刻画(Body Mass Index) 国际上常用的衡量人体肥胖和健康程度的重要标准,主要用于统计分析 定义: BMI=体重(kg)/身高^2(m2) 提出问题: 实例 ...

  6. docker挂载数据卷

    1.Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷,简单的理解就是将数据持久化的工具. 2.在使用docker容器的时候,会产生一系列的数据文件,这些数据文件在我们 ...

  7. Python基础4--数据类型

    一.数据类型是什么鬼? 计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形.音频.视频.网页等各种各样的数据,不同 ...

  8. 知识全聚集 .Net Core 技术突破 丨ABP vNext 开始

    介绍 很久没有更新博客了,之前想更新但是发现博客园崩了,外加工作上的调换也比较忙,最近有了点时间我来继续更新下这个系列的文章. 今年3月份我带着我们研发组同事,将公司产品从老Abp重构到Abp vNe ...

  9. CSS 世界中的方位与顺序

    在 CSS 中,我们经常会与各种方向方位打交道. 譬如 margin.padding,它们就会有 margin-left.margin-right 或者是 padding-left.padding-r ...

  10. Redis 6.0 新特性:带你 100% 掌握多线程模型

    Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注. 码老湿,提供了啥特性呀?知道了我能加薪么? 主要特性如下: 多线程处理网络 IO: 客户端缓存 ...