从零开始制作Linux

2023-11-12

提到制作Linux,大家都能想到如雷贯耳、大名鼎鼎的Linux from scratch。但Linux from scratch的复杂性不是普通人能轻易掌握的,对于初学者来说,任何步骤出现不一致,会让初学者遇到挫拆,攻破LFS的信心越来越低。

本文教大家制作一个比LFS更简单的Linux系统,只有Grub、Linux kernel和最小的根文件系统initrd,原理和过程都非常简单。大家可以在这个基础的Linux之上,再增加其它的功能,逐步走向LFS。

制作Linux,首先该系统是放到磁盘上的,所以涉及启动引导操作系统的知识,我们使用著名的Grub引导程序来实现,Grub执行之后,由它来加载OS kernel和文件系统。 对于OS kernel,我们利用比较新版本的kernel代码来编译即可,而根文件系统,我们使用一个工具来生成最小的initrd。

整个制作过程,首先都有一个磁盘,这个磁盘可以是软盘,可以是USB,也可以是SSD磁盘或者HDD。但考虑到很多朋友身边没有这些东西,我们使用Qemu来试验,原理与直实硬件完全一样,唯一不同的是Qemu的磁盘可以用Host OS的文件来代替。

计算机启动过程

在制作过程中,难免会无法理解纷繁复杂的操作过程,到底为什么需要这样操作,这需要对计算启动过程的原理有些基本的认识。计算机是如何启动的,这个问题在网上可以搜到大量文章,可以参考阮一峰大牛写的《计算机是如何启动的?》,我将关键过程列在这里:

  1. BIOS阶段:计算上电后,系统从主板上的BIOS程序运行,检测系统,初始化运行环境
  2. 加载bootloader阶段:BIOS依次扫描硬盘,如果某个硬件的第一个扇区(512字节)的最后两字节为0x55和0xAA,则该硬盘为启动硬盘,该扇区为主引导记录(Master boot record,缩写为MBR),BIOS将该扇区加载到0x7C00内存处,然后跳到该地址开始执行bootloader
  3. Bootloader加载OS阶段:Bootloader开始执行,由于它只有512字节在内存,所以这512个字节的功能是将它剩下的代码从它后面的扇区(第2扇区,第3扇区,直到……第N扇区)加到到内存,Bootloader 代码完整加载到内存;然后bootloader读配置文件,然后从磁盘中加载kernel文件和根文件系统initrd到内存,最后跳到kernel开始执行OS
  4. OS kernel启动:OS kernel开始做系统初始化,将根文件系统initrd解压缩,加载到根文件,运行init进程

简化版本启动过程,BIOS是主机提供的,而Bootloader,OS kernel和initrd都需要制作安装。

制作涉及的软件说明

本文使用Qemu来验证,所以需要Qemu模拟的硬盘(实际是Linux的一个磁盘文件),Qemu提供BIOS功能,所以只需要安装Bootloader, OS kernel和initrd,各组件选用如下表所示:

组件 软件 版本
Bootloader Grub 2.00
OS kernel linux kernel 4.9
initrd mkinitramfs xx

下面是制作过程是涉及软件的版本

软件 版本 构建方式
Ubuntu 12.04 直接安装
grub-install 2.00~rc1 源码编译安装
qemu 2.10.0 源码编译安装
fdisk 2.20.1 直接安装
losetup xxx 直接安装

开始制作Linux

步骤1:创建64M大小的磁盘文件

使用dd命令,创建一个64M大小的文件,命令如下:

dd if=/dev/zero of=disk.img bs=1M count=64

运行过程如图1所示:
图1:dd命令运行过程

图1:dd命令运行过程

步骤2:对磁盘分区,整个磁盘只建一个分区

使用fdisk命令对disk.img磁盘进行分区,使用n命令创建新的、主分区,该分区为整个磁盘大小,命令如下:

fdisk disk.img

fdisk命令交互过程如图2所示:
图2:fdisk命令运行过程

图2:fdisk命令运行过程

步骤3:将磁盘分区关联到/dev/loop7设备

如果你稍为对磁盘结构有点了解,应该如何磁盘最开始扇区是MBR,里面有分区表,记录磁盘上有多少分区,每个分区从哪个扇区开始,以及分区占用多少扇区,往往第一个分区的开始扇区并不是从第2个扇区开始。

我们使用fdisk -l disk.img命令可以看到该磁盘只有一个主分区,是从第2048个扇区开始:

ivan@ivan:~/minilinux$ fdisk -l disk.img 

Disk disk.img: 67 MB, 67108864 bytes
41 heads, 32 sectors/track, 99 cylinders, total 131072 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xa711cdff

   Device Boot      Start         End      Blocks   Id  System
disk.img1            2048      131071       64512   83  Linux

我们接下来要在该分区建立文件系统,以及往里面放kernel和initrd,这一步需要将该分区与直接的磁盘设备关联,才能对该分区进行格式化和文件拷贝。使用losetup命令将该分区与一个/dev/loop7设备进行关联,命令如下:

losetup -o 1048576 /dev/loop7 disk.img

-o表示该分区在disk.img的偏移量(字节为单,即2048 x 512 = 1048576 )

命令运行如图3所示:
图3:losetup运行过程

图3:losetup运行过程

步骤4:格式化分区和挂载分区

接下来将分区格式化为ext3,命令如下:

mkfs.ext3 /dev/loop7

mkfs.ext3命令运行过程如图4所示:
图4:mkfs.ext3命令运行过程

图4:mkfs.ext3命令运行过程

格式化后,将该分区挂载到mnt目录(在工作目录创建mnt目录,或者直接使用系统提供的/mnt均可,本文使用前者):

mkdir mnt
mount -t ext3 /dev/loop7 ./mnt/

mount命令运行过程如图5所示:

图5:mount命令运行过程

图5:mount命令运行过程

步骤5:安装grub

磁盘和分区已经做完了,下一步是安将Grub,安装命令如下:

grub-install –boot-directory=./mnt/boot/ –target=i386-pc –modules=part_msdos disk.img

运行结果如图6所示:
图6:grub-install命令运行过程

图6:grub-install命令运行过程

运行grub-install命令之后,会将disk.img磁盘第一个扇区修改成MBR,同时将grub代码安装到第一个分区之前的扇区里(通常是0号到64号扇区之间),最后将grub代码运行所需要其它模块和配置文件保存到 ./mnt/boot/grub目录下。

步骤6:下载、编译内核

首先在kernel.org官网下载linux-4.9.tar.gz文件,如下图7所示:
图7:下载kernel

图7:下载kernel

然后使用默认配置编译x86_64内核和拷贝到distk.img磁盘的 boot/目录,命令如下:

make x86_64_defconfig
make bzImage -j4
sudo cp arch/x86/boot/bzImage ~/minilinux/mnt/boot/

上述命令运行过程如图8所示:
图8:内核编译和拷贝过程

图8:内核编译和拷贝过程

步骤7:制作initrd

使用mkinitramfs命令可以制作简化版本的文件系统, 使用mkinitramfs命令生成极简的initrd,放到boot目录下,命令如下:

mkinitramfs -o ./mnt/boot/initrd

运行过程如图9所示:
图9:生成initrd根文件系统

图9:生成initrd根文件系统

步骤8:编写grub.cfg,让Grub引导kernel运行

万事俱备,只欠东风,Grub、kernel和initrd都已准备好的,剩下的事件就是写grub.cfg配置文件,告诉Grub从哪个目录可以找到bzImage和initrd文件,具体命令如下:

cat - > ./mnt/boot/grub/grub.cfg << EOF
menuentry “FreshLinux” {
linux (hd0,msdos1)/boot/bzImage console=tty0
initrd (hd0,msdos1)/boot/initrd
}
EOF

上述是非交互式命令,它的工作是用vim编辑mnt/boot/grub/grub.cfg文件,增加引导bzImage和initrd的描述,交互式的过程如图10所示:

图10:编辑mnt/boot/grub/grub.cfg过程

图10:编辑mnt/boot/grub/grub.cfg过程

hd0表示第一个硬盘,而msdos1表示该硬盘的第一个分区(注:Grub对硬盘和分区的标识略有不同,硬盘从编号0开始,而分区却从编号1开始)。

linux (hd0,msdos1)/boot/bzImage console=tty0
表示:系统第一个硬盘,第一个分区的boot/bzImage文件是内核压缩镜像,而后面的console=tty0是内核启动参数,告诉内核输出到控制台上,而非图形化界面。

initrd (hd0,msdos1)/boot/initrd
表示:系统第一个硬盘,第一个分区的boot/initrd是根文件系统。

到此为止,Linux已经制作完成,只需将解挂载/dev/loop7即可将 刚刚建立的文件内容刷新到disk.img磁盘文件上,命令如下:

umount /dev/loop7
losetup -d /dev/loop7

步骤9:从磁盘运行Linux

OK,包含完整系统的Linux磁盘(disk.img)已制作完成,使用qemu将它运行起来,命令如下:

qemu-system-x86_64 -hda disk.img

然后进入Grub界面,选择我们刚刚制作的FreshLinux,按回车运行。过程如图11所示:
图11: qemu启动和grub操作过程

图11: qemu启动和grub操作过程

接下来进入kernel启动,接着运行init进程,最后运行sh进程,等待用户输入命令,如图12是输入ls和pwd命令的运行过程:
图12:运行ls和pwd命令过程

[图12:运行ls和pwd命令过程

大功告成,一个极简的Linux系统构建完成了……

自动化脚本

建议大家挽起袖子,将上述过程一步步运行,感受DIY带来的乐趣。如果制作过程中遇到问题,可以试试下面这个自动化脚本。

#!/bin/bash

dd if=/dev/zero of=disk.img bs=1M count=64
fdisk disk.img << EOF
n
p



w
EOF
losetup -o 1048576 /dev/loop7 disk.img
mkfs.ext3 /dev/loop7
mkdir ./mnt
mount -t ext3 /dev/loop7 ./mnt/
mkdir -p ./mnt/boot/grub
grub-install --boot-directory=./mnt/boot/ --target=i386-pc --modules=part_msdos  disk.img
mkinitramfs -o ./mnt/boot/initrd
# 请根据内核编译路径进行修改
cp /home/ivan/kernel/linux-4.9/arch/x86/boot/bzImage ./mnt/boot
cat - > ./mnt/boot/grub/grub.cfg << EOF
menuentry "FreshLinux" {
    linux (hd0,msdos1)/boot/bzImage console=tty0
    initrd (hd0,msdos1)/boot/initrd 
}
EOF
umount /dev/loop7
losetup -d /dev/loop7
qemu-system-x86_64  -hda disk.img

写在后面

估计有读者会问,制作这个极简的Linux有何意义呢? 我认为至少有以下两个意义:
1. 使用几个简单的命令,理清操作系统的运行过程,相比LFS来说,更容易成功,成就感极强
2. 可以在此极简的Linux上,增加更复杂的功能,一步步逼近LFS,千里之行,始下足下

如果试验成功,那如何在真正的硬盘上制作Linux呢?相信你会给出正确的答案,原理与本文全完一样,只是磁盘相关的步骤从disk.img文件,替取成了真正的磁盘/dev/hdX或者/dev/sdX而已,其它不变。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从零开始制作Linux 的相关文章

  • 为什么我收到的数据包数据大小大于mss?

    我在两台 PC 上使用 ifconfig ethX mtu 300 修改了 MTU 并使用 netperf 测试网络 我用 WireShark 嗅探了 SYN 数据包中的 MSS 260 但我得到了一些大于 260 的数据包 为什么 嗅探器
  • Elasticsearch 无法写入日志文件

    我想激活 elasticsearch 的日志 当我运行 elasticsearch 二进制文件时 我意识到我在日志记录方面遇到问题 无法加载配置 这是输出 sudo usr share elasticsearch bin elasticse
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令并流输出?

    下列的这个帖子 https stackoverflow com questions 15092961 how to continuously display python output in a webpage 我能够tail f网页的日志
  • 如何在bash中使用jq从变量中包含的json中提取值

    我正在编写一个 bash 脚本 其中存储了一个 json 值 现在我想使用 Jq 提取该 json 中的值 使用的代码是 json val code lyz1To6ZTWClDHSiaeXyxg redirect to http examp
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • 无法从 jenkins 作为后台进程运行 nohup 命令

    更新 根据下面的讨论 我编辑了我的答案以获得更准确的描述 我正在尝试从詹金斯运行 nohup 命令 完整的命令是 nohup java jar home jar server process 0 35 jar prod gt gt var
  • qemu 访客自动化

    我找不到任何文档说明存在可用于在 qemu guest 内部实现自动化操作的 API 例如 我想从主机启动来宾计算机内部的进程 Libvirt 似乎不包含此类功能 注意 无需使用任何虚拟化 API 即可实现自动化 从我的博客文章 http
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • Android 时钟滴答数 [赫兹]

    关于 proc pid stat 中应用程序的总 CPU 使用率 https stackoverflow com questions 16726779 total cpu usage of an application from proc
  • 所有平台上的java

    如果您想用 java 为 Windows Mac 和 Linux 编写桌面应用程序 那么所有这些代码都相同吗 您只需更改 GUI 即可使 Windows 应用程序更像 Windows 等等 如果不深入细节 它是如何工作的 Java 的卖点之
  • PHP 从命令行启动 gui 程序,但 apache 不启动

    首先 我阅读了有类似问题的人的一些帖子 但所有答案都没有超出导出 DISPLAY 0 0 和 xauth cookies 这是我的问题 提前感谢您的宝贵时间 我开发了一个小库 它使用 OpenGL 和 GLSL 渲染货架 过去几天我将它包装
  • Linux 内核标识符中前导和尾随下划线的含义是什么?

    我不断遇到一些小约定 比如 KERNEL Are the 在这种情况下 是内核开发人员使用的命名约定 还是以这种方式命名宏的语法特定原因 整个代码中有很多这样的例子 例如 某些函数和变量以 甚至 这有什么具体原因吗 它似乎被广泛使用 我只需
  • CentOS:无法安装 Chromium 浏览器

    我正在尝试在 centOS 6 i 中安装 chromium 以 root 用户身份运行以下命令 cd etc yum repos d wget http repos fedorapeople org repos spot chromium
  • 使用 sh 运行 bash 脚本

    我有 bash 脚本 它需要 bash 另一个人尝试运行它 sh script name sh 它失败了 因为 sh 是他的发行版中 dash 的符号链接 ls la bin sh lrwxrwxrwx 1 root root 4 Aug
  • iptables通过注释删除特定规则

    我需要删除一些具有相同评论的规则 例如 我有带有 comment test it 的规则 所以我可以像这样获得它们的列表 sudo iptables t nat L grep test it 但是我怎样才能删除所有带有注释 测试它 的 PR

随机推荐

  • HDR dump失败解决办法

    运行脚本 进HDR拍照 一般在 sdcard Android data com oplus camera files spdebug hdrdump 目录下 可以生成dump 若没有生成dump 清除相机缓存 重新运行脚本 重启手机即可 脚
  • Apache Solr入门教程(初学者之旅)

    Apache Solr入门教程 初学者之旅 写在前面 本文涉及solr入门的各方面 建议边思考边实践 相信能帮助你对solr有个清晰全面的了解并能简单实用 在Apache Solr初学者教程的这个例子中 我们将讨论有关如何安装最新版本的Ap
  • 0-1分布的方差和期望

    最后欢迎大家访问我的个人网站 1024s
  • CentOS7.4下C++开源日志库easyloggingpp的使用

    CentOS7 4下C 开源日志库easyloggingpp的使用 一 简单示例 二 多线程支持 Linux后台开发过程中经常需要日志记录一些运行信息 网上找到easyloggingpp只需要包含头文件和实现文件即可 使用很方便 现整理如下
  • vue:结合elementUI设计网站登录页

    这次主要是记录三个重点 1 组件间通信的方法 其一 2 脚手架搭建的vue工程的组件调用 3 elementUI 的轮播图与模态框的设计 先看效果图 简单的就做了这四个页面 总共四个组件实现这些效果 这个小网站需要的组件也挺多的 当然不止这
  • Pandas——读/写不同数据源的数据

    Pandas 读 写不同数据源的数据 一 读 写数据库数据 1 SQLAlchemy连接MySQL数据库 2 使用 read sql query read sql table read sql 函数 读 取数据库数据 3 使用 to sql
  • 全面总结机器学习超参数调优(附代码)

    公众号 尤而小屋作者 Peter编辑 Peter 大家好 我是Peter 本文的主题 机器学习建模的超参数调优 开局一张图 文章很长 建议直接收藏 一 什么是机器学习超参数 机器学习超参数是在开始学习过程之前设置值的参数 而不是通过训练得到
  • 导入elementui组件库

    1 在终端输入 vue add element 回车 选择按需加载 输入yes 回车 选择zh CN 回车 2 在plugins里面有一个element js 我们先去elementui官网看到快速上手 就可以看到按需引入 复制代码放到el
  • 搭建 react+ts+less+Antd 项目

    搭建 react ts less Antd 项目 一 文章目录 搭建 react ts less Antd 项目 一 前言 项目搭建流程 1 新建项目 2 暴露配置 3 支持less 4 支持ts 前言 项目搭建流程 1 新建项目 npx
  • 谷粒商城-分布式高级篇[商城业务-检索服务]

    谷粒商城 分布式基础篇 环境准备 谷粒商城 分布式基础 业务编写 谷粒商城 分布式高级篇 业务编写 持续更新 谷粒商城 分布式高级篇 ElasticSearch 谷粒商城 分布式高级篇 分布式锁与缓存 项目托管于gitee 一 商城业务 检
  • ckplayer html播放本地,vue中使用ckplayer播放器

    请选择视频源 获取视频中 请稍等 export default name components props sourceUrl type String default height type String default 550px dat
  • python统计套利_基于python的统计套利实战(二)之协整检验

    协整关系协整 Cointegration 理论是恩格尔 Engle 和格兰杰 Granger 在1978年提出的 平稳性是进行时间序列分析的一个很重要的前提 很多模型都是基于平稳下进行的 而现实中 很多时间序列都是非平稳的 所以协整是从分析
  • 智能优化算法改进-K-means聚类种群初始化附Matlab代码

    目录 0引言 一 K means聚类原理 二 K Means聚类算法步骤 三 K Means聚类原理图 编辑 四 K means聚类改进智能优化算法种群初始化效果图 4 1 初始种群数据图 4 2 K means聚类结果图 4 2 1 根据
  • VM虚拟机下 Ubuntu下摄像头显示安装

    安装 可通过PPA进行安装 ffmpeg sudo add apt repository ppa kirillshkrogalev ffmpeg next sudo apt get update sudo apt get install f
  • 浅谈Router和Route

    router 和 route 是在前端框架中用于管理和处理路由的两个关键概念 这两者之间的关系可以通过具体的代码来解释 在本示例中 我将使用 React 和 React Router 来说明它们之间的关系 Router 路由器 Router
  • DataPipeline丨DataOps的技术考量

    作者 DataPipeline CEO 陈诚 从 数据的资产负债表与现状 到 DataOps理念与设计原则 直至 DataOps的组织架构与挑战 我们对于DataOps的讨论已经进行了三周 不难发现 在此期间 我们探讨的话题始终围绕在上层建
  • SSR、SSE、SST、R2

    在MATLAB中 计算回归问题的拟合优度 或判定系数 可用 B BINT R RINT STATS regress Y X 指令 其中的STATS的第一个返回值即为R2 R2约接近于1 拟合效果越好 SSR为回归平方和 SSE为残差平方和
  • React官网入门项目井字棋游戏

    React官网里有很详细的教程 也有在线沙盒 但是写的东一榔头西一棒槌的 不适合新手入门 所以我还是建议大家可以先去看看阮一峰大神的React博客或者某硅谷的网课 这个网课讲的很详细 甚至详细到有些啰嗦 我大概是用20天把网课看完 然后再看
  • 进程信号(信号产生、注册、注销、处理),信号阻塞和volatile关键字

    文章目录 进程信号 信号产生 信号在进程中注册 信号在进程的注销 信号的处理 信号的处理方式 信号阻塞 如何阻塞一个信号 int sigprocmask int how sigset t set sigset t old int sigem
  • 从零开始制作Linux

    提到制作Linux 大家都能想到如雷贯耳 大名鼎鼎的Linux from scratch 但Linux from scratch的复杂性不是普通人能轻易掌握的 对于初学者来说 任何步骤出现不一致 会让初学者遇到挫拆 攻破LFS的信心越来越低