【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 256

struct 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();

    // 启动其他CPU
    secondary_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; do
    if [ -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内核打下坚实的基础。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】DHCP服务实验

DHCP实验 实验前提 1、两个linux操作系统,一个为服务器端,一个为客户端 2、两个操作系统设置为仅主机模式 3、在客户端-虚拟网络编辑器-仅主机模式VMnet1-关闭DHCP 实验步骤 新建虚拟机

2022年计算机网络408考研真题解析

第一题&#xff1a; 解析&#xff1a;网络体系结构-数据链路层 在ISO网络参考模型中&#xff0c;运输层&#xff0c;网络层和数据链路层都实现了流量的控制功能&#xff0c;其中运输层实现的是端到端的流量控制&#xff0c;网络层实现的是整个网络的流量控制&#xff0c;数据链…

详解 【AVL树】

AVL树实现 1. AVL的概念AVL树的实现2.1 AVL树的结点结构2.2 AVL树的插入2.2.1 AVL树的插入的一个大概操作&#xff1a;2.2.2 AVL树的平衡因子更新2.2.3 平衡因子的停止条件2.2.4 再不考虑旋转的角度上实现AVL树的插入 2.3 旋转2.3.1 旋转的原则2.3.2 右单旋2.2.3 右单旋代码实现…

Python 爬虫从入门到(不)入狱学习笔记

爬虫的流程&#xff1a;从入门到入狱 1 获取网页内容1.1 发送 HTTP 请求1.2 Python 的 Requests 库1.2 实战&#xff1a;豆瓣电影 scrape_douban.py 2 解析网页内容2.1 HTML 网页结构2.2 Python 的 Beautiful Soup 库 3 存储或分析数据&#xff08;略&#xff09; 一般爬虫的基…

一区北方苍鹰算法优化+创新改进Transformer!NGO-Transformer-LSTM多变量回归预测

一区北方苍鹰算法优化创新改进Transformer&#xff01;NGO-Transformer-LSTM多变量回归预测 目录 一区北方苍鹰算法优化创新改进Transformer&#xff01;NGO-Transformer-LSTM多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab NGO-Transformer-LST…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作HUD Camera以及让两个相机同时渲染屏幕二、制作HUD Canvas 1.制作法力条Soul Orb引入库2.制作生命条Health读入数据3.制作吉欧统计数Geo Counter4.制作…

Opencv+ROS自编相机驱动

目录 一、工具 二、原理 代码 标定 三、总结 参考&#xff1a; 一、工具 opencv2ros ubuntu18.04 usb摄像头 二、原理 这里模仿usb_cam功能包对Opencv_ros进行修饰&#xff0c;加上相机参数和相机状态&#xff0c;难点在于相机参数的读取。 对于相机参数话题 camera…

计算机网络 | 7.网络安全

1.网络安全问题概述 &#xff08;1&#xff09;计算机网络面临的安全性威胁 <1>计算机网络面临的完全性威胁 计算机网络面临的两大类安全威胁&#xff1a;被动攻击和主动攻击 被动攻击 截获&#xff1a;从网络上窃听他人的通信内容。主动攻击 篡改&#xff1a;故意篡改…

机器翻译基础与模型 之四:模型训练

1、开放词表 1.1 大词表和未登陆词问题 理想情况下&#xff0c;机器翻译应该是一个开放词表&#xff08;Open Vocabulary&#xff09;的翻译任务。也就是&#xff0c;无论测试数据中包含什么样的词&#xff0c;机器翻译系统都应该能够正常翻译。 现实的情况是即使不断扩充词…

ThingsBoard规则链节点:Azure IoT Hub 节点详解

目录 引言 1. Azure IoT Hub 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 数据传输 3.2 数据分析 3.3 设备管理 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff0c;提供了设备…

MongoDB相关问题

视频教程 【GeekHour】20分钟掌握MongoDB Complete MongoDB Tutorial by Net Ninja MongoDB开机后调用缓慢的原因及解决方法 问题分析&#xff1a; MongoDB开机后调用缓慢&#xff0c;通常是由于以下原因导致&#xff1a; 索引重建&#xff1a; MongoDB在启动时会重建索引…

三种蓝牙架构实现方案

一、蓝牙架构方案 1、hostcontroller双芯片标准架构 手机里面包含很多SoC或者模块&#xff0c;每颗SoC或者模块都有自己独有的功能&#xff0c;比如手机应用跑在AP芯片上&#xff0c;显示屏&#xff0c;3G/4G通信&#xff0c;WiFi/蓝牙等都有自己专门的SoC或者模块&#xff0…

sql工具!好用!爱用!

SQLynx的界面设计简洁明了&#xff0c;操作逻辑清晰易懂&#xff0c;没有复杂的图标和按钮&#xff0c;想对哪部分操作就在哪里点击右键&#xff0c;即使你是数据库小白也能轻松上手。 尽管SQLynx是一款免费的工具&#xff0c;但是它的功能却丝毫不逊色于其他付费产品&#xff…

UE5肉鸽游戏教程学习

学习地址推荐&#xff1a;UE5肉鸽项目实战教程_哔哩哔哩_bilibili

反向代理服务器的用途

代理服务器在网络中扮演着重要的角色&#xff0c;它们可以优化流量、保护服务器以及提高安全性。在代理服务器中&#xff0c;反向代理服务器是一种特殊类型&#xff0c;常用于Web服务器前&#xff0c;它具备多种功能&#xff0c;能够确保网络流量的顺畅传输。那么&#xff0c;让…

面试经典 150 题:205,55

205. 同构字符串 【解题思路】 来自大佬Krahets 【参考代码】 class Solution { public:bool isIsomorphic(string s, string t) {map<char, char> Smap, Tmap;for(int i0; i<s.size(); i){char a s[i], b t[i];//map容器存在该字符&#xff0c;且不等于之前映射…

信创改造 - TongRDS 替换 Redis

记得开放 6379 端口哦 1&#xff09;首先在服务器上安装好 TongRDS 2&#xff09;替换 redis 的 host&#xff0c;post&#xff0c;passwd 3&#xff09;TongRDS 兼容 jedis # 例如&#xff1a;更改原先 redis 中对应的 host&#xff0c;post&#xff0c;passwd 改成 TongRDS…

嵌入式工程师面试笔试总结——day1

第一章、进程与线程 1、什么是进程、线程&#xff0c;有什么区别&#xff1f; 进程是资源&#xff08; CPU 、内存等&#xff09;分配的基本单位&#xff0c;线程是 CPU 调度和分配的基本单位&#xff08;程序执行的最小单 位&#xff09;。同一时间&#xff0c;如果CPU 是单…

算法之区间和题目讲解

题干 难度&#xff1a;简单 题目分析 题目要求算出每个指定区间内元素的总和。 然而&#xff0c;区间在输入的最下面&#xff0c;所以按照暴力破解的思路&#xff0c;我们首先要遍历数组&#xff0c;把它的值都存进去。 然后&#xff0c;遍历下面的区间&#xff0c;从索引a…

泷羽sec-linux

基础之linux 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…