Linux 内核预备知识:浅析 offsetof 宏以及新手的所思所想
最近一头扎进了 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 宏以及新手的所思所想的更多相关文章
- (转)linux内核虚拟文件系统浅析
转自http://hi.baidu.com/_kouu/item/4e9db87580328244ef1e53d0 ###### 虚拟文件系统(VFS)在我看来, "虚拟"二字主要 ...
- (转)linux内核虚拟文件系统浅析【转】
转自:https://www.cnblogs.com/woainilsr/p/3590716.html 转自http://hi.baidu.com/_kouu/item/4e9db8758032824 ...
- Linux内核零碎知识
UNIX系统:内核.shell外壳.文件系统.工具或应用程序. 操作系统功能:进程与处理机管理.存储管理.设备管理.作业管理.文件管理. 内存是磁盘的缓存,cache是内存的缓存. 可把内核看作是不断 ...
- C语言:类似linux内核的分等级DEBUG宏(打印宏)
总结几种log打印printf函数的宏定义 http://blog.chinaunix.net/uid-20564848-id-73402.html #include <stdio.h> ...
- linux内核自锁旋spinlock常用宏解释
转自:http://blog.sina.com.cn/s/blog_6929134b0100tdn8.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持, ...
- [Linux] 001 预备知识
Unix 1965年 MIT,通用电气(GE),AT&T 的贝尔实验室联合开发 项目名称:Multics 目标:开发一种交互式的,具有多道程序处理能力的分时操作系统 后来:贝尔实验室宣布退出 ...
- Linux内核驱动基础(一)常用宏定义【转】
转自:http://blog.csdn.net/tommy_wxie/article/details/9427081 一: __init和__initdata : __exit和__exitdata ...
- 【转】linux内核中writesb(), writesw(), writesl() 宏函数
writesb(), writesw(), writesl() 宏函数 功能 : writesb() I/O 上写入 8 位数据流数据 (1字节) writesw() I/O 上写入 16 ...
- [Linux] 002 预备知识
1. 开源软件 (1) 常见开源软件 Apache NGINXTM MySQL PHP Saamba mongoDB Python Ruby Sphinx -- (2) 开源软件的特点 绝大多数开源软 ...
随机推荐
- RabbitMq脑裂问题
现象 部署在阿里云上的2台RabbitMQ主从,访问management页面时出现如下所示的内容: 查看其中一个mq的日志,发现如下内容: 00:06:32.423 [warning] <0.5 ...
- qtscrcpy使用
点击"USB线"一栏中的"刷新设备列表"按钮,随后设备序列号会显示出来: ·点击"获取设备IP",随后在"无线"一栏中会 ...
- 高校表白App-团队冲刺第八天
今天要做什么 尝试连接数据库(MySQL) 做了什么 连接成功 遇到的问题 Android连接数据库可以采用JDBC连接,因为在Android开发中,大多数连接到远程MySQL数据库的方法是加入特定的 ...
- Java基础00-多态19
1. 多态 多态 1.1 多态概述 代码示例: 动物类: public class Animal { public void eat(){ System.out.println("动物吃东西 ...
- Linux安装Tomcat-Nginx-FastDFS-Redis-Solr-集群——【第十一集补充:修改fastdfs的http.conf文件进行防盗链,重启nginx失败】
1,进入fastdfs的安装目录: 2,修改http.conf文件,详情可参考: https://www.cnblogs.com/xiaolinstudy/p/9341779.html 3,重启ngi ...
- airodump-ng的使用及显示
PWR 表示所接收的信号的强度.表示为负数,数值赿大表示信号赿强.(绝对值赿大,数据赿值小) beacons 表示网卡接收到的AP发出的信号个数
- python + mysql 实现表更新数据
实例如下: import pymysqldef Update_Set(): #打开数据库链接 db = pymysql.connect("localhost","root ...
- 微信小程序云开发-云存储-上传、下载、打开文件文件(word/excel/ppt/pdf)一步到位
一.wxml文件 <!-- 上传.下载.打开文件一步执行 --> <view class="handle"> <button bindtap=&quo ...
- Leetcode:面试题28. 对称的二叉树
Leetcode:面试题28. 对称的二叉树 Leetcode:面试题28. 对称的二叉树 Talk is cheap . Show me the code . /** * Definition fo ...
- VSCode 使用
运行时,如何弹出cmd命令窗口:将launch.json文件中的 externalConsole设置为true,并按F5运行(不要按右上角的运行按钮) 如何cin:先再命令窗口通过g++ *.cpp生 ...