Linux内核开发_1_编译LInux内核

2022-09-15 11:04:05 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

目录

1. 准备工作

1.1 学习环境

1.2 下载Linux内核源码

1.3 解压Linux内核

1.4 目录结构介绍

2. Linux内核配置

2.1 配置选项

1. make config

2. make menuconfig

3. make gconfig

3 开始配置

1. 配置解释

General setup 通用选项

Enable loadable module support 可加载模块

Enable the block layer 块设备层

System Type arm 占用配置,一般是厂家提供,与第7项代替了原有的Processor type and features

[ ]FIQ Mode Serial Debugger,一般不选。

6Bus support 总线支持

x86_64_defconfig

我的配置

4. 编译Linux源码

4.1 Linux编译生成文件解释

5 运行Linux内核

5.1 qemu

6. 简单的文件系统和init

建议

编译问题收录及解决方案



1. 准备工作

1.1 学习环境

本系列教程使用的环境如下: 操作系统版本:

代码语言:javascript复制
Linux ubuntu 18.04

Linux内核版本:

代码语言:javascript复制
cat /proc/version
Linux version 4.15.0-20-generic (buildd@lgw01-amd64-039)
 (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018

学习的Linux内核版本:

代码语言:javascript复制
linux-4.10.15

1.2 下载Linux内核源码

首先我们需要下载Linux-4.10.15内核,我们可以直接使用wget下载:

代码语言:javascript复制
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.10.15.tar.xz

下载完成之后就会看到一个名为 “linux-4.10.15.tar.xz”的文件,可以看到后缀格式是.tar.xz,双重压缩格式

1.3 解压Linux内核

双重压缩格式,我们依次解压先用“xz”命令解压:

代码语言:javascript复制
xz -d linux-4.10.15.tar.xz

-d是代表解压的意思 解压完成后就会在当前目录看到一个名为“linux-4.10.15.tar”,解压完xz后还有一重tar,在使用tar命令解压一次就可以得到原内核文件,这里建议解压到/usr/src目录下,这里没有别的意思,是Linux内核开发者们给我的建议,这个在行业里是一个开发标准,一般Linux源代码都是放在这个目录下,你可以在任何发行版里的这个目录下看到他们所使用的Linux内核源码

代码语言:javascript复制
sudo tar -xf linux-4.10.15.tar -C /usr/src/.

之后我们就可以在/usr/src目录下看到我们的linux源码了,同时还有发行版的

随后我们进入到这个目录下,查看一下这个目录的文件体系

代码语言:javascript复制
cd linux-4.10.15

1.4 目录结构介绍

/arch

不同CPU架构下的核心代码。其中的每一个子目录都代表Linux支持的CPU架构

/block

块设备通用函数

/crypto

常见的加密算法的C语言实现代码,譬如crc32、md5、sha1等

/Documentation

说明文档,对每个目录的具体作用进行说明

/drivers

内核中所有设备的驱动程序,其中的每一个子目录对应一种设备驱动

/firmware

固件代码

/fs

Linux支持的文件系统代码,及各种类型的文件的操作代码。每个子目录都代表Linux支持的一种文件系统类型

/include

内核编译通用的头文件

/init

内核初始化的核心代码

/ipc

内核中进程间的通信代码

/kernel

内核的核心代码,此目录下实现了大多数Linux系统的内核函数。与处理器架构相关的内核代码在/kernel/$ARCH/kernel

/lib

内核共用的函数库,与处理器架构相关的库在/kernel/$ARCH/lib

/mm

内存管理代码,譬如页式存储管理内存的分配和释放等。与具体处理器架构相关的内存管理代码位于/arch/$ARCH/mm目录下

/net

网络通信相关代码

/samples

示例代码

/scripts

用于内核配置的脚本文件,用于实现内核配置的图形界面

/security

安全性相关的代码

/tools

Linux中的常用工具

/usr

内核启动相关的代码

/virt

内核虚拟机相关的代码

2. Linux内核配置

2.1 配置选项

Linux内核提供了三种配置的模式

1. make config

此模式非常不建议使用,除非你的时间非常多,因为这个方式是通过终端输出的方式挨个,也就是逐个问你设置选项,非常的耗时,而且不方便

我们测试一下,在终端窗口输入:

代码语言:javascript复制
make config

呀哈,出了一个错:

“Command ‘make’ not found, but can be installed with:”

提示我们系统上没有make这个命令,这是因为我在学习时使用的是新系统,下载的是简洁版的,里面只带了基本的工具链,这样的第三方命令没有包含其中,所以需要我们自己下载,这里下载一下就好啦:

代码语言:javascript复制
sudo apt install make

安装完成之后输入上面说的命令:

又出错了,这次是告诉我们没有GCC:“/bin/sh: 1: gcc: not found”

我们在安装一下,这里不需要指定gcc版本,直接输入gcc,软件仓库会给我们根据当前系统内核选择一个最优的gcc版本下来,提示y/n,选择y即可:

代码语言:javascript复制
sudo apt install gcc

安装完成之后在接着输入“make config”:

代码语言:javascript复制
make config

这次出现了一个报错:

“scripts/basic/fixdep.c:449:1: fatal error: opening dependency file scripts/basic/.fixdep.d: Permission denied”

不要急,一开始没有看到后面的“Permission denied”时,我以为是文件代码出错了,后面仔细一看,是告诉我们没有权限,因为是在用户目录下,所以需要加上“sudo”来获取权限

代码语言:javascript复制
sudo make config

加上sudo后正确运行了:

他会逐步询问所有的配置信息,要求你输入y/n,这样是非常耗时的,而且很多选项初学者可能根本不太了解,所以非常不建议使用这个选项,我们按下ctrl c退出这个配置工具。

2. make menuconfig

这个配置工具是基于menu文字图形库编写的,非常推荐这个选项,界面友好,其次是第一次使用这个选项会提供一些默认参数,无论是对初学者还是经验丰富的开发者们来说,都是一个非常好的选择

代码语言:javascript复制
sudo make menuconfig

报了一个错误信息:

“<command-line>:0:12: fatal error: curses.h: No such file or directory”

提示找不到curses.h,这个是menu里的库文件,这很明显告诉我们缺少menu的库

所以这里我们安装一下:

代码语言:javascript复制
sudo apt install libncurses5-dev

在此运行后输出:

“Your display is too small to run Menuconfig! It must be at least 19 lines by 80 columns. scripts/kconfig/Makefile:28: recipe for target ‘menuconfig’ failed”

意思是,这个配置工具在编写时对我们的终端窗口大小进行了限制,长和宽必须满足19*80

这里我们把终端窗口扩大一点,或者直接f11进入全屏模式都可以(全屏模式下按f11会退出全屏模式),然后在运行:

成功进入到配置界面

注:linux内核中一个功能模块有三种编译方法:一种是编入、一种去去除、一种是模块化。所谓编入就是将这个模块的代码直接编译连接到zImage中去,去除就是将这个模块不编译链接到zImage中,模块化是将这个模块仍然编译,但是不会将其链接到zImage中,会将这个模块单独链接成一个内核模块.ko文件,将来linux系统内核启动起来后可以动态的加载或卸载这个模块。 在menuconfig中选项前面的括号里,*表示编入,空白表示去除,M表示模块化。

3. make gconfig

代码语言:javascript复制
sudo make gconfig

报错:

/bin/sh: 1: pkg-config: not found * * Unable to find the GTK installation. Please make sure that * the GTK 2.0 development package is correctly installed… * You need gtk -2.0, glib-2.0 and libglade-2.0. * 原因也很明确的告诉我们需要安装gtk 2.0的库,这里我们安装一下:

代码语言:javascript复制
sudo apt-get install libgtk2.0-dev libglib2.0-dev libglade2-dev

安装完成之后运行一次看一下效果:

代码语言:javascript复制
sudo make gconfig

3 开始配置

1. 配置解释

General setup 通用选项

选项

作用

[*]Prompt for development and/or incomplete code/drivers

设置界面中显示还在开发或者还没有完成的代码与驱动,最好选上,许多设备都需要它才能配置。

[ ]Cross-compiler tool prefix

交叉编译工具前缀,如果你要使用交叉编译工具的话输入相关前缀。默认不使用。嵌入式linux更不需要。

[ ]Local version – append to kernel release

自定义版本,也就是uname -r可以看到的版本,可以自行修改,没多大意义。

[ ]Automatically append version information to the version string

自动生成版本信息。这个选项会自动探测你的内核并且生成相应的版本,使之不会和原先的重复。这需要Perl的支持。由于在编译的命令make-kpkg 中我们会加入- – append-to-version 选项来生成自定义版本,所以这里选N。

Kernel compression mode (LZMA)

选择压缩方式。

[ ]Support for paging of anonymous memory (swap)

交换分区支持,也就是虚拟内存支持,嵌入式不需要。

[*]System V IPC

为进程提供通信机制,这将使系统中各进程间有交换信息与保持同步的能力。有些程序只有在选Y的情况下才能运行,所以不用考虑,这里一定要选。

[*]POSIX Message Queues

这是POSIX的消息队列,它同样是一种IPC(进程间通讯)。建议你最好将它选上。

[*]BSD Process Accounting

允许进程访问内核,将账户信息写入文件中,主要包括进程的创建时间/创建者/内存占用等信息。可以选上,无所谓。

[*]BSD Process Accounting version 3 file format

选用的话统计信息将会以新的格式(V3)写入,注意这个格式和以前的 v0/v1/v2 格式不兼容,选不选无所谓。

[ ]Export task/process statistics through netlink (EXPERIMENTAL)

通过通用的网络输出工作/进程的相应数据,和BSD不同的是,这些数据在进程运行的时候就可以通过相关命令访问。和BSD类似,数据将在进程结束时送入用户空间。如果不清楚,选N(实验阶段功能,下同)。

[ ]Auditing support

审计功能,某些内核模块需要它(SELINUX),如果不知道,不用选。

[ ]RCU Subsystem

一个高性能的锁机制RCU 子系统,不懂不了解,按默认就行

[ ]Kernel .config support

将.config配置信息保存在内核中,选上它及它的子项使得其它用户能从/proc/ config.gz中得到内核的配置,选上,重新配置内核时可以利用已有配置

[ ]Enable access to .config through /proc/config.gz

上一项的子项,可以通过/proc/ config.gz访问.config配置,上一个选的话,建议选上。

16)Kernel log buffer size (16 => 64KB, 17 => 128KB)

内核日志缓存的大小,使用默认值即可。12 => 4 KB,13 => 8 KB,14 => 16 KB单处理器,15 => 32 KB多处理器,16 => 64 KB,17 => 128 KB。

[ ]Control Group support(有子项)

控制组支持,使用默认即可

Example debug cgroup subsystem

cgroup子系统调试例子

Namespace cgroup subsystem

cgroup子系统命名空间

Device controller for cgroups

cgroups设备控制器

Cpuset support

只有含有大量CPU(大于16个)的SMP系统或NUMA(非一致内存访问)系统才需要它。

[ ]enable deprecated sysfs features to support old userspace tools

在某些文件系统上(比如debugfs)提供从内核空间向用户空间传递大量数据的接口,一般不选

[ ]Kernel->user space relay support (formerly relayfs)

内核系统区和用户区进行传递通讯的支持,这个选项在特定文件系统(relayfs)中提供数据接口支持,它可以支持从内核空间到用户空间的大批量数据传递的支持。不清楚可以不选。

[ ]Namespaces support,(有子项)

命名空间支持,允许服务器为不同的用户信息提供不同的用户名空间服务。

[]Initial RAM filesystem and RAM disk (initramfs/initrd) support

初始RAM的文件和RAM磁盘( initramfs /initrd)支持(如果要采用initrd启动则要选择,否则可以不选),不需要,不用选。嵌入式linux一般不选。

[ ]Optimize for size

-Os代替-O2参数,可能会有二进制错误问题,一般不选。

(0)Default panic timeout

添0即可。

[*]Configure standard kernel features (for small systems)

特殊内核用到,可以不选,嵌入式linux则必选。

[ ]Enable the Anonymous Shared Memory Subsystem

启用匿名共享内存子系统,不清楚可以不选。

[ ]Enable AIO support

支持AIO(Asynchronous I/O 异步事件非阻塞I/O),(包含aio.h, aio_read,向内核发出读命令,aio_write向内核写命令,详细见‘AIO介绍‘文档),AIO机制为服务器端高并发应用程序提供了一种性能优化的手段。加大了系统吞吐量,所以一般用于大型服务器,一般不用选。

[ ]Kernel Performance Events And Counters(有子项)

性能相关的事件和计数器支持(既有硬件的支持也有软件的支持).大多数现代CPU都会通过性能计数寄存器对特定类型的硬件事件(指令执行,缓存未命中,分支预测失败)进行计数,同时又丝毫不会减慢内核和应用程序的运行速度.这些寄存器还会在某些事件计数到达特定的阈值时触发中断,从而可以对代码进行性能分析. Linux Performance Event 子系统对上述特性进行了抽象,提供了针对每个进程和每个CPU的计数器,并可以被 tools/perf/ 目录中的”perf”工具使用.

[*]Enable VM event counters for /proc/vmstat

允许在/proc/vmstat中包含虚拟内存事件记数器。

[*]Enable SLUB debugging support

支持SLUB内存分配管理器调试,

[ ]Disable heap randomization

禁用随即head,选不选均可。

Choose SLAB allocator (SLUB (Unqueued Allocator)) —>

选择内存分配管理器,强烈推荐使用SLUB。

[ ]Profiling support

剖面支持,用一个工具来扫描和计算计算机的剖面图,支持系统测评,一般开发人员使用,不选。

[ ]Kprobes

Kprobes 提供了一个强行进入任何内核例程并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以 轻松地收集处理器寄存器和全局数据结构等调试信息。开发者甚至可以使用 Kprobes 来修改 寄存器值和全局数据结构的值。 选中后linux内核 将附带此工具

GCOV-based kernel profiling —>

基于GCOV的代码覆盖率,可以来审评代码

Enable loadable module support 可加载模块

选项

作用

[ ]Forced module loading

强行加载模块,不建议选。

[*]Module unloading

支持模块卸载,必须选上。

[ ]Forced module unloading

强行卸载模块,即使内核认为这样并不安全,也就是说你可以把正在使用中的模快卸载掉。如果你不是内核开发人员或者骨灰级的玩家,不建议选。

[ ]Module versioning suppor

这个功能可以让你使用其它版本的内核模块,除非特殊需要,一般不选。

[ ]Source checksum for all modules

这个功能是为了防止更改了内核模块的代码但忘记更改版本号而造成版本冲突,现在很少使用,不选。

Enable the block layer 块设备层

选项

作用

[*]Support for large (2TB ) block devices and files

仅在使用大于2TB的块设备时需要

[*]Block layer SG support v4

通用SCSI设备第四版支持。

[*]Block layer data integrity support

块设备数据完整性支持。

[*]IO Schedulers —>(有子项)

IO调度器

[ ]Anticipatory I/O scheduler

抢先式I/O调度器,大多数块设备只有一个物理查找磁头(例如一个单独的SATA硬盘),将多个随机的小写入流合并成一个大写入流,用写入延时换取最大的写入吞吐量.适用于大多数环境,特别是写入较多的环境(比如文件服务器)

[ ]Deadline I/O scheduler

期限式I/O调度器,轮询的调度器,简洁小巧,提供了最小的读取延迟和尚佳的吞吐量,特别适合于读取较多的环境(比如数据库)。

]CFQ I/O scheduler

使用QoS策略为所有任务分配等量的带宽,避免进程被饿死并实现了较低的延迟,可以认为是上述两种调度器的折中.适用于有大量进程的多用户系统CFQ调度器尝试为所有进程提供相同的带宽。它将提供平等的工作环境,对于桌面系统很合适。

Default I/O scheduler (CFQ) —>

默认IO调度器有上面三个IO调度器:抢先式是传统的,它的原理是一有响应,就优先考虑调度。如果你的硬盘此时在运行一项工作,它也会暂停下来先响应用户。期限式则是:所有的工作都有最终期限,在这之前必须完成。当用户有响应时,它会根据自己的工作能否完成,来决定是否响应用户。CFQ则是平均分配资源,不管你的响应多急,也不管它的工作量是多少,它都是平均分配,一视同仁的。

System Type arm 占用配置,一般是厂家提供,与第7项代替了原有的Processor type and features

<无子选项>

[ ]FIQ Mode Serial Debugger,一般不选。

<无子选项>

6Bus support 总线支持

选项

作用

PCI support

PCI总线支持,主板上最长用的插槽,最好选上,arm linux可以不选,arm一般没有PCI总线。

PCCard (PCMCIA/CardBus) support

微通道总线,一般老式笔记本有这种插槽,笔记本选上,arm linux 不选。

Kernel Features 内核特征

选项

作用

[ ] Tickless System (Dynamic Ticks)

非固定平率系统,能让内核运行的更有效率,并且省电,pc下可选,特别是笔记本,arm linux一般不用选。

[ ] High Resolution Timer Support

支持高频率时间发生器,需要硬件兼容,但大多数PC和ARM都不支持,不选

Memory split (2G/2G user/kernel split) —>

内核与用户空间各占2G,内核空间0-0x7FFFFFFF,用户空间80000000-FFFFFFFF

Preemption Model (No Forced Preemption (Server)) —>

内核抢占模式。普通PC用户一般选2,arm linux 选1就可以。

No Forced Preemption (Server)

禁止内核抢占,适合服务器环境。针对于高吞吐量的设计,但有可能延时较长,适用于服务器或科学运算,或向要最大的运算能力,而不理会调度上的延时。

Voluntary Kernel Preemption (Desktop)

自愿内核抢占,适合普通的桌面环境。已降低吞吐量为代价,降低内核调度的最大延时,提供更快的应用程序响应,即使系统已经高负荷运转,应用程序仍然能运行的很“流畅”,适合用户桌面环境

Preemptible Kernel (Low-Latency Desktop)

主动内核抢占,适合运行实时程序的环境。更低的吞吐量,进一步降低内核的调度延时,使应用程序更加流畅。

[ ]Compile the kernel in Thumb-2 mode

编译Thumb-2 mode内核,一般不选

*] Use the ARM EABI to compile the kernel

与下面绑定配置。

[*] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)

对于嵌入式系统(jffs2 yaffs2),这两个要选上,否则很可能启动的时候报错(kernel panic- not syncing: Attempted to kill init!)

[ ] High Memory Support (EXPERIMENTAL)(有子项)

1G物理内存以下不选,超过1G才选。(配置略有变化,以前的选项是OFF(<1G),4G(>1G && <4G),64G(>4G))。1

Allocate 2nd-level pagetables from highmem

1G物理内存以下不选,超过1G(小于4G)才选

Allocate 3nd-level pagetables from highmem

大于4G,选择此项目。

Memory model (Flat Memory) —>

一般选”Flat Memory”,其他选项涉及内存热插拔。

[ ] Enable KSM for page merging

允许linux内核识别出包含相同内容的内存页,然后合并这些内存页,将数据整合在一个位置可以多次引用,特殊功能,不用选。

(4096) Low address space to protect from user allocation

设置低端内存大小,默认4096即可

[ ] Use kernel mem{cpy,set}() for {copy_to,clear}_user() (EXPERIMENTAL)

这个资料目前暂时没找到合理的解释,可以先忽略不选

x86_64_defconfig

这里我们可以使用Linux下一些自带的配置文件,可以使用“make x86_64_defconfig” 这样就生成了一个x86_64的amd架构的linux内核(64位),如果要生成arm平台的架构的话需要修改配置文件,这里我目前还没有打算学习arm架构的配置工作,所以先选中amd的,如果要生成i386的可以使用”make i386_defconfig”

你可以在arch/架构名/configs目录下找到对应的配置文件,也可以直接copy到你的根目录名字改为.config就可以了,这些是Linux内核自带的一些基础配置

我的配置

下面这个是我的配置,因为在Linux下配置不当,虽然编译可以过但是运行会出现问题,如内核恐慌,或者VFS加载失败等,这里是我在之前实验中编译成功且运行没有问题的一次配置,如果你编译时遇到了问题,可以参考下面的配置:

链接: 百度网盘 请输入提取码

提取码: 6zfg

下载后用unzip命令解压:

代码语言:javascript复制
unzip config.zip

然后目录下会出现一个config的文件,在copy到我们的linux内核根目录下,copy的新名字要在前面加个“.”,这样Linux内核才能识别:

代码语言:javascript复制
cp config 你的linux内核目录源码/.config

4. 编译Linux源码

编译方法:

代码语言:javascript复制
sudo make bzImage -j4

这里解释一下,如果直接sudo make是无法生成bzImage的,在之前的版本里可以,但是在4.4版本上无法这样,bzImage是x86_64架构的压缩镜像文件

选项里“j”,代表多线程编译,n代表线程数

如:“make -j32”

拆分32个线程来编译这个项目,线程数量请根据自己机器的配置,如果配置不好开这么多线程,调度起来很慢,也容易卡住。

一般情况下,建议你的处理器如果是4核,那么建议每个核分出2个线程也就是8线程

“make j8”

这样让你的CPU压力不至于那么大。

如果make的时候看到输出信息,你觉得很乱不舒服,可以使用重定向“>”的方法来屏蔽这些输出信息

make > test.txt

当出错时make会停下来,就可以到这个文件里去查看输出信息了

如果不想生成输出文件又想屏蔽输出信息,Linux开发者没有为我们提供这个选项,但是我们可以利用Linux系统下的dev/null 黑洞文件,把输出信息重定向进去

make > /dev/null

黑洞文件相当于windows回收站,但是这个文件不会保存数据,凡是进去的任何数据,就会被自动删除,找不回的.

输入命令后,Make就会开始自动化编译

这个期间,可以去喝杯咖啡,因为编译非常耗时

一步到位,没有出现任何编译出错的问题,这就是选择相仿内核版本的好处

4.1 Linux编译生成文件解释

arch里有不同架构的文件夹,如arm,x86,x86_64,当编译完成之后这些文件的boot目录里会生成这些压缩文件,根目录下会生成vmlinux文件,这个文件是未压缩的目标文件

下面是这些文件的作用:

vmlinux

编译出来的最原始的内核文件,未压缩。

zImage

是vmlinux经过gzip压缩后的文件。

bzImage

bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。

uImage

U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的tag。

vmlinuz

是bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。

initrd

是“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。

5 运行Linux内核

5.1 qemu

我们精简版的操作系统是不带这些第三方工具的,所以我们先安装一下:

代码语言:javascript复制
sudo apt install qemu

安装完成之后,arch下x86是指32位的Linux内核,而x86_64是指64位的内核,64位是可以运行32位程序的,未来32位架构将逐渐被淘汰

这里通过给qemu的-kernel指定内核参数,上面我们说过编译产生的文件是压缩文件,qemu可以正确运行吗?

答:可以,qemu会自动帮我们解析,我们只需要使用-kernel指定就好了

-kernel是指定内核文件的意思

代码语言:javascript复制
qemu-system-x86_64 -kernel arch/x86/boot/bzImage

运行结果:

可以看到内核成功跑起来了,但是报了一个错误

end Kernel painc – not syncing: VFS:Unable to mount root fso on unknown-block(0,0)

Linux内核在运行时需要文件根系统的支持,但是这里我们并没有生成文件系统,所以Linux会报这个错误

除了文件系统以外Linux还会在初始化完成之后并且成功加载文件系统之后会去fork一个进程,名为init,这里先不做详细讨论,后续的学习文章里对Linux内核这块做一个详细的解析

这个init就是守护进程,所有用户空间下的进程都由它来主动创建,就类似我们刚刚打开终端产生的shell一样

6. 简单的文件系统和init

这里先教大家制作一个简单的文件系统和init

Linux内核对文件系统有一定的格式要求,如NFS和SVR格式的文件系统,这是基于UNIX演化来的,所以我们需要把文件系统制作成NFS/SVR/EXT等文件系统格式,这里推荐一个命令:“CPIO”,这个命令可以帮助我们生成SVR格式的文件系统,我们的配置选项里默认使用SVR的文件系统格式。

在制作根文件系统之前,我们需要一个init,先用c语言制作一个init:

代码语言:javascript复制
vim init.c

代码:

代码语言:javascript复制
#include <stdio.h>

int main(){
    printf("nhello Linux Kernel!n");
    while(1);
}

注意这里末尾一定要加while(1),否则无法正确执行init和输出,经过分析inux内核的0号进程(init)运行时会初始化相关工作,然后在去fork一个子进程并把cpu控制权交给子进程。

同时,init作为父进程不能被结束,因为一旦死掉,用户态空间下就没一个进程,而这个init就是我们Linux上被称为守护进程的东西,一旦死掉,整个用户态下所有的进程都会被一并杀死。

这样的话用户态就相当于没了,那么内核就会产生异常了,会报内核恐慌,attempted kill init这样的问题,来告诉我们init有问题

这是我根据资料查到的Linux内核第一次调度INIt的一个过程。

这里我们只是简单的写一个init程序,后面我们使用buysbox来完成相关初始化,目前正在研究buysbox是如何去完成这些初始化的,等研究完成,会写一篇文章来告诉大家。

这里我们使用静态编译,因为我们等下要使用的文件系统是临时制作的,里面除了包含init以外不会有任何库,所以不可以动态加载,必须使用静态:

代码语言:javascript复制
gcc -static -O0 init.c -o init

这里使用“-O0″.不要让编译器给我们的init进行优化,防止编译器偷懒优化掉某些指令,但是这段代码比较少,也没啥可以优化的,也可以不加。

这里我们制作一个临时的根目录文件:

代码语言:javascript复制
echo init | cpio -o --format=newc > rootfs

注意CPIO的格式,CPIO选项 -o 是从输出流里读取数据,而echo init是把init文件输出到输出流里,然后CPIO从输出流将文件读取到rootfs里,这里 –format=newc 是指使用SVR4的格式,而>是流重定向。

注意这里在使用这个命令前不要创建目录,不然会出错,cpio会自动帮我们生成对应格式的文件

输出:

代码语言:javascript复制
1651 blocks

如果生成成功,会告诉我们输出的文件大小

这里给上它权限,保证qemu在运行时加载到rootfs时有足够的权限

代码语言:javascript复制
sudo chmod 777 rootfs

这里我们需要使用的运行命令是:

代码语言:javascript复制
qemu-system-x86_64   
     -kernel ./bzImage 内核文件
     -initrd ./rootfs  临时根文件系统
     -append "root=根文件系统 rdinit=第一个init程序"

这里给大家解释一下这些选项的意思,-kernel上面说过了,-initrd的意思是临时的根文件系统,Linux内核在加载根文件系统之前,VFS会去使用临时的文件系统做相关的初始化工作,当一切就绪后才会去调用实际的根文件系统。

这里我们将它指向我们的临时文件系统,我们这个简单的文件系统可以先给Linux内核使用,实际的根文件系统是通过-append选项指定的,这个选项可以给内核运行参数,其中root就是指向了根文件系统,这里我们也可以给它指定我们用于临时的文件系统,但是根文件系统不是这么简单的,所以我们上面的简单文件系统只有一个init,真正的根文件系统还需要一些其它的设备文件,这里我们先不做多讨论,后面文件系统这块我们在深入探讨。

这里rdinit的意思是告诉内核启动后从根文件系统里寻找一个可执行程序的文件名

我们输入命令:

代码语言:javascript复制
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd ./rootfs1 -append "rdinit=/init"

因为我电脑上没有根文件系统,所以root就没传参进去。

运行结果:

可以看到我们的“hello linux kernel!”打印出来了!

这是一件非常值得高兴的事,因为我编译了许多天,我在一边编译一遍学习它的内核源码,虽然进度很缓慢,但是我觉得这是一件能让人成长的事情,非常值得高兴,我踩了很多坑,所以这里非常建议大家在编译时一定要选择与发行版内核相仿的Linux内核版本编译。

因为内核向前兼容,如果你书上用的老版本代码,那么在新版本一样可以用,甚至新版本上的代码会比老版本的代码更好,更健壮,因为Linux主版本会收录许多次版本上的优点,同时也会修复许多bug,Linux在不断的完善。

建议

这一段话是我在经历许多天的编译Linux内核过程中习得的总结,可以跳过。 起初我学习Linux内核的时候,是参考“Linux内核设计与实现”这本书来学的,书上使用的Linux内核版本是:“2.34.6”,这个版本已经很老了,最初我使用的是ubnutu20来编译它的,虽然编译过程中遇到了很多问题,但是都一 一解决了,最后运行时会出现许许多多的问题,如:VFS无法加载根文件系统而引起的文件恐慌,还有kill init这样的问题,最初我以为是配置的问题,在我根据查找到资料,和仔细学习了一边如何配置Linux内核后,我发现其实这些基本上用默认的就可以了。 大多数的除了特定需求一般无需裁剪,尤其是刚入门的学生,最好是使用默认配置,后来我觉得可能是最新版的ubtunu使用的软件仓库里的lib版本太高了,虽然可以编译过,但是有一小部分的lib库可能对旧函数不支持或者说已经废弃了,这是我目前认为的原因,我只是在ubtunu上安装了旧版本的gcc,但是并没有选择降级glib等库,这可能是原因之一,但是如果我使用了旧版本的glib库还有openssl等,那么一些ubtunu上较新的软件可能使用了新版本glib库里的一些新增的特性,导致这些使用动态加载的软件们无法正常运行。 所以最后我选择使用老版本的centOS,可是无论是centOS或者是ubtunu对这些老版本的操作系统所使用的仓库代码已经废弃了,所以在这个上面是无法下载任何lib的,我必须修改源,使用国内或者国外带有老版本仓库的源才可以,即便使用了这些,ubtunu或者centOS这些老版本的操作系统在编译过程中也会出现一些零零散散的问题,由于版本太老使用起来非常不顺手,最后我向一些Linux内核的开发者们寻求意见,他们给出的一件事建议去编译3.0或者4.0以上的Linux版本内核,因为太老的Linux内核在目前较新的操作系统平台上已经很难在编译出来了,原因是因为Linux依赖gnu的软件体系,而gnu的软件体系在不断的升级进化,每次的升级,都会被用在正在开发中最新的Linux内核,而除了原始版的Linux内核不是在Linux上编译出来的以外,其余的Linux内核版本都是在Linux内核上开发而来的,而使用的开发工具(gcc)就是那个时代最新的版本。 其次是不同的发行版它自带的一些依赖LIB都是比较新的,因为它也要为自己的Linux内核提供一个运行环境,而问题的关键是,当我们编译Linux内核时,不确定编译器会不会把当前操作系统上的一些依赖LIB编译到Linux内核里去。 如正在编译Linux2.6,他使用的glib是3,那么我们当前的发行版使用的是6 一旦编译起来,gcc会从系统环境里把6的一些依赖lib链接到2.6里去了,从而导致某些函数可能在未来的运行结果或者参数要求发生了一点变化,因为有向前兼容的方法,但是这个函数或者编译器选项很可能会被废弃掉。 不确定的因素很多,所以这里我给大家的建议是,如果你想编译Linux内核,最好选择一个与它使用的Linux内核版本相仿的Linux发行版来编译它 如我选择学习Linux内核,并且选择的Linux内核版本是4.10.15,那么我需要一个与它使用的内核版本相仿的操作系统,所以这里我选择ubtunu18.04,它使用的内核版本是4.15.0。

编译问题收录及解决方案:

1.fatal error: openssl opensslv.h: No

代码语言:javascript复制
sudo apt install libssl-dev

2. Unsupported relocation type: R_X86_64_PLT32

原因是因为linux要进行重定位地址,就是逻辑地址空间变换成内存中的实际物理地址空间,但是如果生成pic代码,也就是动态链接的格式代码,则会生成与位置无关的偏移代码,这个代码依赖程序加载到内存的hand,同时编译器不允许进行重定位。

所以我们要修改不让它生成pic就可以了。

1.在根目录的Makefile文件里找到“cc”,大概在349行

2.在后面加上“ -fno-pic”保存退出

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/163589.html原文链接:https://javaforall.cn

0 人点赞