【Linux】内核的编译和加载

在这里插入图片描述

Linux内核是操作系统的核心,负责管理系统的硬件资源,并为用户空间的应用程序提供必要的服务。内核的编译和加载是操作系统开发和维护的重要环节。本文将详细介绍Linux内核的编译过程以及如何加载内核到系统中。

1. 引言

Linux内核的编译是一个复杂的过程,涉及到配置、预处理、编译、链接等多个步骤。加载内核则是启动操作系统的关键一步,它决定了系统的启动方式和初始状态。通过理解内核的编译和加载过程,我们可以更好地掌握Linux系统的工作原理。

2. Linux内核编译过程

Linux内核的编译过程通常包含以下几个主要步骤:

2.1 配置内核

配置内核是编译过程的第一步,它决定了哪些功能将被编译进内核,哪些功能将以模块的形式加载。配置内核可以通过以下几种方式:

2.1.1 使用menuconfigncurses工具

这些工具提供了图形化的配置界面,可以让用户选择启用或禁用特定的功能。

make menuconfig
2.1.2 手动编辑配置文件

如果熟悉内核配置项,也可以直接编辑.config文件。

nano .config
示例配置项

配置文件中的一些典型配置项如下:

CONFIG_SMP=y # 启用多处理器支持
CONFIG_PREEMPT=y # 启用抢占式调度
CONFIG_CMDLINE=y # 启用从引导加载程序传递的命令行参数
CONFIG_DEVTMPFS=y # 启用/dev文件系统的自动创建

2.2 预处理

预处理阶段主要包括头文件的处理、宏定义的展开、条件编译的判断等。内核使用预处理器(如GCC的预处理器)来处理源代码文件,生成经过预处理的源代码文件。

2.2.1 预处理命令

预处理命令包括#include#define#ifdef等。例如:

#define MAX_DEVICES 256struct device {char name[MAX_DEVICES]; // 设备名称的最大长度int id;                 // 设备ID
};

2.3 编译

编译阶段是将预处理后的源代码文件转换成机器语言的过程。这个过程通常由编译器(如GCC)完成。Linux内核的编译过程非常复杂,因为它包含了大量的源代码文件和依赖关系。

2.3.1 编译命令

使用make命令进行编译,可以指定并行编译的数量来加快编译速度:

make -j$(nproc)
2.3.2 编译过程

编译过程涉及以下几个步骤:

  1. 编译内核源代码:将C/C++源代码编译成汇编代码。
  2. 汇编汇编代码:将汇编代码转换成目标文件(.o)。
  3. 处理汇编文件:对生成的目标文件进行处理,如添加调试信息等。
示例代码
// 文件:drivers/chardev.c#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>static int major = 240; // 为设备分配主设备号static dev_t dev_num = MKDEV(major, 0); // 构造设备号
static struct cdev c_dev;                // 字符设备结构体
static struct class *class;              // 设备类指针
static struct device *device;            // 设备指针static int dev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device opened.\n");return 0;
}static int dev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device closed.\n");return 0;
}static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{// 实现读逻辑return count;
}static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{// 实现写逻辑return count;
}static const struct file_operations fops = {.owner          = THIS_MODULE,.read           = dev_read,.write          = dev_write,.open           = dev_open,.release        = dev_release,
};static int __init dev_init(void)
{// 注册字符设备register_chrdev_region(MKDEV(major, 0), 1, "my_char_dev");// 初始化字符设备结构cdev_init(&c_dev, &fops);// 添加字符设备到设备类class = class_create(THIS_MODULE, "my_char_class");device = device_create(class, NULL, dev_num, NULL, "my_char_dev");// 注册字符设备cdev_add(&c_dev, dev_num, 1);return 0;
}static void __exit dev_exit(void)
{// 删除字符设备cdev_del(&c_dev);// 移除设备device_destroy(class, dev_num);// 销毁设备类class_unregister(class);// 注销字符设备区域unregister_chrdev_region(dev_num, 1);
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");

2.4 链接

链接阶段是将编译后的各个目标文件合并成一个可执行文件的过程。对于Linux内核而言,这个过程将生成最终的内核映像文件(通常是vmlinuz)。链接阶段还包括生成符号表、重定位等操作。

2.4.1 链接命令

使用make命令进行链接:

make bzImage
2.4.2 生成最终映像

最终的内核映像通常会被压缩,并加上引导加载程序所需要的头部信息。生成的最终映像文件可以是vmlinuzzImage等形式。

cp arch/x86/boot/bzImage /boot/vmlinuz

2.5 创建模块

除了核心内核之外,还有许多功能是以模块的形式存在的,这些模块可以在系统运行时动态加载。创建模块的过程包括编译模块源代码,并生成模块文件(通常扩展名为.ko)。

2.5.1 模块编译命令
make modules
2.5.2 安装模块

将编译好的模块安装到系统中:

make modules_install

2.6 生成模块依赖关系

生成模块依赖关系,确保模块在加载时可以找到所需的其他模块。

make modules_prepare

3. Linux内核加载过程

加载内核是启动操作系统的关键一步,它由引导加载程序(Bootloader)完成。引导加载程序负责加载内核到内存,并将控制权传递给内核。以下是加载内核的主要步骤:

3.1 加载引导加载程序

计算机启动时,BIOS/UEFI会加载引导加载程序到内存,并执行引导加载程序。常用的引导加载程序有GRUB、LILO等。

3.1.1 GRUB示例

使用GRUB加载内核:

grub> kernel /boot/vmlinuz root=/dev/sda1 ro
grub> initrd /boot/initrd.img
grub> boot

3.2 加载内核映像

引导加载程序读取并加载内核映像到内存中。内核映像通常位于存储设备的某个分区或扇区中。

3.2.1 内核映像的结构

内核映像通常包含以下部分:

  • 压缩的内核映像:使用gzipbzip2压缩的内核映像。
  • 引导加载程序的头部信息:包含了引导加载程序所需的引导参数。

3.3 初始化内核

加载内核映像后,引导加载程序会跳转到内核的入口点,开始执行内核代码。内核初始化过程包括设置内存管理、初始化设备驱动、加载模块等。

3.3.1 内核初始化过程

内核初始化过程包括:

  1. 设置内存页表:初始化内存管理。
  2. 初始化硬件设备:初始化CPU、内存控制器等。
  3. 初始化中断向量表:设置中断处理机制。
  4. 初始化系统调用表:设置系统调用表,以便用户空间程序调用。
  5. 初始化进程管理:设置进程调度器,初始化进程管理数据结构。
  6. 初始化文件系统:挂载根文件系统,初始化文件系统管理数据结构。
  7. 初始化网络堆栈:初始化网络协议栈,设置网络设备。
示例代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/processor.h>
#include <asm/io.h>
#include <asm/setup.h>
#include <asm/irq.h>void __init early_printk(const char *fmt, ...)
{static char *console_output = NULL;static int console_output_baud = 0;static int console_output_line = 0;char *p;va_list args;va_start(args, fmt);p = vprintf(fmt, args);va_end(args);if (console_output) {put_port(console_output, p);console_output_line++;if (console_output_line >= 25)console_output_line = 0;}
}asmlinkage void __init start_kernel(void)
{extern void __init trap_setup(void);extern void __init mem_init(void);extern void __init mem_setup(void);extern void __init setup_arch(char **cmdline);extern void __init secondary_cpu_boot(void);// 初始化架构相关setup_arch(&command_line);// 设置内存管理mem_setup();// 设置内存页表mem_init();// 设置中断向量表trap_setup();// 初始化硬件设备early_irq_setup();// 初始化系统调用表setup_syscalls();// 初始化进程管理init_idle_boot_cpu();// 初始化文件系统initrd_load();// 初始化网络堆栈init_network_namespace();// 启动其他CPUsecondary_cpu_boot();
}

3.4 启动初始进程

内核初始化完成后,会启动初始进程initinit进程的PID为1,它是所有用户空间进程的父进程。init进程会读取/etc/inittab文件,根据配置启动相应的守护进程和服务。

3.4.1 init进程示例
static int start_init(void)
{struct file *filp;struct dentry *dentry;struct inode *inode;struct task_struct *task;// 创建初始进程task = alloc_task_struct();task->state = TASK_RUNNING;task->pid = 1;task->comm = "init";// 打开并执行"/sbin/init"filp = filp_open("/sbin/init", O_RDONLY | O_EXEC, 0755);if (IS_ERR(filp))return PTR_ERR(filp);// 创建进程并执行task->thread = kthread_create(execve, filp, "init");if (IS_ERR(task->thread))return PTR_ERR(task->thread);// 启动进程wake_up_process(task->thread, TASK_UNINTERRUPTIBLE, 0);return 0;
}

3.5 系统初始化

init进程启动后,会继续执行一系列初始化脚本和配置文件,完成系统的初始化工作,包括启动网络服务、挂载文件系统、启动用户界面等。

3.5.1 inittab文件示例
::system:/sbin/init
::respawn:/sbin/getty 38400 tty1
::respawn:/sbin/getty 38400 tty2
::respawn:/sbin/getty 38400 tty3
::respawn:/sbin/getty 38400 tty4
::respawn:/sbin/getty 38400 tty5
::respawn:/sbin/getty 38400 tty6

3.6 系统初始化脚本

系统初始化脚本通常位于/etc/rc.d/rc.local/etc/init.d目录下,这些脚本会在init进程启动后被执行。

示例初始化脚本
#!/bin/sh# 检查是否启用网络
if [ "$NETWORKING" = "yes" ]; then/etc/init.d/networking start
fi# 检查是否启用SSH
if [ "$SSH" = "yes" ]; then/etc/init.d/ssh start
fi# 如果启用了显示管理器,则启动显示管理器
if [ "$DISPLAY_MANAGER" = "yes" ]; then/etc/init.d/gdm start
fi# 执行用户定义的脚本
for script in /etc/rc.local.d/*.sh; doif [ -x "$script" ]; then. "$script"fi
done# 进入多用户模式
exec /sbin/init -- rc 3

4. Linux内核模块管理

Linux内核模块是可动态加载和卸载的内核组件,允许内核在运行时扩展其功能。模块化设计使得Linux内核具有很高的灵活性。

4.1 模块编译

模块编译通常使用make modules命令来完成。编译完成后,模块文件会保存在lib/modules/目录下。

make modules

4.2 模块加载

模块可以使用insmodmodprobe等命令加载到内核中。加载模块后,内核会根据模块提供的功能扩展其能力。

insmod /path/to/module.ko
modprobe module_name

4.3 模块卸载

模块可以使用rmmod命令从内核中卸载。

rmmod module_name

4.4 模块初始化和清理

模块需要实现module_initmodule_exit函数,用于模块的初始化和卸载。

示例代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>// 模块初始化函数
static int __init mod_init(void)
{printk(KERN_INFO "Module loaded.\n");return 0;
}// 模块退出函数
static void __exit mod_exit(void)
{printk(KERN_INFO "Module unloaded.\n");
}// 初始化模块入口
module_init(mod_init);// 卸载模块入口
module_exit(mod_exit);// 指定模块许可
MODULE_LICENSE("GPL");

5. 小结

Linux内核的编译和加载是操作系统启动的关键步骤。通过理解内核的编译过程和加载机制,我们可以更好地掌握Linux系统的工作原理,并在开发和维护Linux系统时更加得心应手。希望本文能够为读者提供一个全面了解Linux内核编译和加载的视角,并为深入学习Linux内核打下坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/61932.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt桌面应用开发 第七天(绘图事件 绘图设备)

目录 1.绘图事件paintEvent 2.高级绘图 3.图片绘制 4.绘图设备 4.1QPixmap 4.2QBitmap 4.3QImage 4.4QPicture 1.绘图事件paintEvent paintEvent——绘图事件 需求&#xff1a;利用QPainter绘制点、线、圆、矩形、文字&#xff1b;设置画笔改为红色&#xff0c;宽度为…

使用IDEA构建springboot项目+整合Mybatis

目录 目录 1.Springboot简介 2.SpringBoot的工作流程 3.SpringBoot框架的搭建和配置 4.用Springboot实现一个基本的select操作 5.SpringBoot项目部署非常简单&#xff0c;springBoot内嵌了 Tomcat、Jetty、Undertow 三种容器&#xff0c;其默认嵌入的容器是 Tomcat&#xff0c;…

【含开题报告+文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现

开题报告 随着互联网的普及和数字娱乐产业的蓬勃发展&#xff0c;电影作为一种重要的娱乐方式&#xff0c;已经深入人们的日常生活。然而&#xff0c;面对海量的电影资源&#xff0c;用户在选择观影内容时常常感到困惑和无所适从。传统的电影推荐方式&#xff0c;如人工筛选、…

C++使用minio-cpp(minio官方C++ SDK)与minio服务器交互简介

目录 minio简介minio-cpp简介minio-cpp使用 minio简介 minio是一个开源的高性能对象存储解决方案&#xff0c;完全兼容Amazon S3 API&#xff0c;支持分布式存储&#xff0c;适用于大规模数据架构&#xff0c;容易集成&#xff0c;而且可以方便的部署在集群中。 如果你已经部…

【君正T31开发记录】8.了解rtsp协议及设计模式

前边搞定了驱动&#xff0c;先不着急直接上手撸应用层的代码&#xff0c;先了解一下大致要用到的东西。 设计PC端先用vlc rtsp暂时H264编码&#xff08;vlc好像不支持h265,这个后边我试试&#xff09;的视频流&#xff0c;先需要支持上rtsp server&#xff0c;了解rtsp协议是必…

JavaScript中的this指向绑定规则(超全)

JavaScript中的this指向绑定规则&#xff08;超全&#xff09; 1.1 为什么需要this? 为什么需要this? 在常见的编程语言中&#xff0c;几乎都有this这个关键字&#xff08;Objective-C中使用的是self),但是在JavaScript中的this和常见的面向对象语言中的this不太一样 常见面…

Spring注入Map学习

Spring注入Map学习 在Spring中 在策略模式中, 会经常用到 根据Bean名称获取Bean的实例 有2个方法很好用 1. 使用Autowired注入 2. 使用构造方法注入 但是奇怪的一点是: 日志打印并没有看到结果, 第一行的 Autowired的结果 是个null 那是因为 注入时机 的问题 注入时机&…

Redis五大基本类型——Set集合命令详解(命令用法详解+思维导图详解)

目录 一、Set集合类型介绍 二、常见命令 1、SADD 2、SMEMBERS 3、SISMEMBER 4、SCARD 5、SRANDMEMBER 6、SPOP 7、SMOVE 8、SREM ​编辑 9、集合间操作 &#xff08;1&#xff09;SINTER &#xff08;2&#xff09;SINTERSTORE &#xff08;3&#xff09;SUNION…

sql 查询语句:将终端数据形式转换成insert语句

文本转换&#xff1a;sql 查询语句&#xff1a;将终端数据形式转换成insert语句 如上&#xff0c;写过后端的都知道&#xff0c;从生产或其他地方拿到的数据&#xff0c;有可能会是图一&#xff1b;但实际上&#xff0c;我们需要图二的数据&#xff1b; 不废话&#xff0c;直接…

C++数据结构与算法

C数据结构与算法 1.顺序表代码模版 C顺序表模版 #include <iostream> using namespace std; // 可以根据需要灵活变更类型 #define EleType intstruct SeqList {EleType* elements;int size;int capacity; };// Init a SeqList void InitList(SeqList* list, int capa…

【AIGC】大模型面试高频考点-RAG篇

【AIGC】大模型面试高频考点-RAG篇 &#xff08;1&#xff09;RAG的基本原理&#xff08;2&#xff09;RAG有哪些评估方法&#xff1f;&#xff08;3&#xff09;RAG有哪些评估框架&#xff1f;&#xff08;4&#xff09;RAG各模块有哪些优化策略&#xff1f; &#xff08;1&am…

标准操作规程(SOP)制定方法+模板指南

在企业的成功之路上&#xff0c;拥有制定、传播以及管理流程文档与详细步骤指南的能力至关重要。众多组织都将标准操作规程&#xff08;SOP&#xff09;作为指导其工作流程操作的核心文档形式。 但SOP的作用远不止于操作指南&#xff1b;它们更像是高性能车辆中的精密GPS系统。…

硬件工程师零基础入门:一.电子设计安全要点与欧姆定律

硬件工程师零基础入门:一.电子设计安全要点与欧姆定律 第一节 电子设计安全要点第二节 欧姆定律 第一节 电子设计安全要点 电路小白最好先买直流稳压电源&#xff08;将高压转成低压直流电&#xff09;使用&#xff0c;尽量不要使用市电。 1.尽量不要捏住电源两端。 正确做法&a…

ShuffleNet:一种为移动设备设计的极致高效的卷积神经网络

摘要 https://arxiv.org/pdf/1707.01083 我们介绍了一种名为ShuffleNet的计算效率极高的卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;该架构专为计算能力非常有限的移动设备&#xff08;例如10-150 MFLOPs&#xff09;而设计。新架构利用两种新操作&#xff1a;逐…

学习Zookeeper

Zookeeper有手就行 1. 初识ZooKeeper1.1 安装ZooKeeper1.2 ZooKeeper命令操作1.2.1 Zookeeper数据模型1.2.2 Zookeeper 服务端常用命令1.2.3 Zookeeper客户端常用命令 2. ZooKeeperJavaAPl操作2.1 Curator介绍2.2 CuratorAPI常用操作2.2.0 引入Curator支持2.2.1 建立连接2.2.2 …

ctfshow-Misc入门(1-16)

misc1 查看图片得到flag misc2 1、打开文本&#xff0c;发现以“塒NG”开头 3、修改文件格式为png格式 4、查看图片&#xff0c;得到flag *遇到的问题&#xff1a;无法直接修改后缀名 *解决方法&#xff1a;需要点击文件夹&#xff0c;然后点击查看&#xff0c;将文件拓…

实践指南:EdgeOne与HAI的梦幻联动

在当今快速发展的数字时代&#xff0c;安全和速度已成为网络服务的基石。EdgeOne&#xff0c;作为腾讯云提供的边缘安全加速平台&#xff0c;以其全球部署的节点和强大的安全防护功能&#xff0c;为用户提供了稳定而高效的网络体验。而HAI&#xff08;HyperApplicationInventor…

词云图大师(WordCloudMaster): 探索创意无限的词云世界!

在信息化时代&#xff0c;如何以一种新颖且富有创意的方式表达数据、文字或想法&#xff1f;答案是词云图&#xff01;而词云图大师(WordCloudMaster)&#xff0c;正是您的绝佳选择。 无论是个人创意项目&#xff0c;还是专业工作中的数据可视化&#xff0c;词云图大师都能以强…

二分法(折半法)查找【有动图】

二分法&#xff0c;也叫做折半法&#xff0c;就是一种通过有序表的中间元素与目标元素进行对比&#xff0c;根据大小关系排除一半元素&#xff0c;然后继续在剩余的一半中进行查找&#xff0c;重复这个过程直至找到目标值或者确定目标值不存在。 我们从结论往回推&#xff0c;…

labview关于文件路径的问题

在调用文件或拆分文件的时候经常会用到拆分路径函数和创建路径函数&#xff0c;最常用的也是当前应用程序目录或者是当前VI目录。 这里我们看到应用程序目录和VI目录在同一项目中&#xff0c;应用程序目录更像是根目录&#xff0c;往下拆分成了各个VI的子目录。 接下来我们来拆…