当前位置: 首页 > news >正文

12.从零开始写LINUX内核--控制台初始化

从零构建 Linux 0.12:控制台初始化的底层实现与代码解析

在 Linux 0.12 内核的启动流程中,控制台初始化是连接硬件与用户的第一座桥梁。它通过识别显示器类型、配置显存映射和端口参数,为后续的内核信息输出与用户交互奠定基础。本文将基于完整源码,系统解析控制台初始化的核心逻辑与实现细节。

一、核心代码架构

控制台初始化的实现采用分层设计,形成了从入口调用到底层硬件操作的完整链路:

1. 初始化入口链

main.c:内核启动的起点,触发终端初始化流程

c

运行

#define __LIBRARY__
void main(void)
{tty_init();  // 启动终端初始化__asm__("int $0x80 \n\r"::);  // 触发系统调用(预留接口)__asm__ __volatile__("loop:\n\r""jmp loop"  // 进入无限循环,等待中断::);
}

tty_io.c:终端初始化中间层,简化调用逻辑

c

运行

#include <linux/tty.h>
void tty_init()
{con_init();  // 直接调用控制台初始化函数
}

2. 底层硬件操作定义(asm/io.h)

该文件通过嵌入式汇编封装了硬件端口的读写操作,为控制台初始化提供硬件访问能力:

c

运行

/* 硬件IO端口访问的嵌入式汇编宏函数 *//** * 硬件端口字节输出* @param[in]	value	欲输出字节* @param[in]	port	端口*/
#define outb(value, port) \__asm__ ("outb %%al,%%dx"::"a" (value),"d" (port))/** * 硬件端口字节输入* @param[in]	port	端口* @retval		返回读取的字节*/
#define inb(port) ({ 												\unsigned char _v; 												\__asm__ volatile ("inb %%dx,%%al":"=a" (_v):"d" (port));		\_v; 															\})/*** 硬件端口字节输出(带延迟)* 使用两条跳转语句来延迟一会儿* @param[in]	value	欲输出字节* @param[in]	port	端口*/
#define outb_p(value, port) 										\__asm__ ("outb %%al,%%dx\n"										\"\tjmp 1f\n"											\"1:\tjmp 1f\n" 											\"1:"::"a" (value),"d" (port))/*** 硬件端口字节输入(带延迟)* 使用两条跳转语句来延迟一会儿* @param[in]	port	端口* @retval		返回读取的字节*/
#define inb_p(port) ({												\unsigned char _v;												\__asm__ volatile (												\"inb %%dx,%%al\n"											\"\tjmp 1f\n"												\"1:\tjmp 1f\n"												\"1:":"=a" (_v):"d" (port));									\_v; 															\})

3. 控制台初始化核心实现(console.c)

该文件实现了显示器识别、参数配置和光标控制等核心功能:

c

运行

#include <linux/tty.h>
#include <asm/io.h>  // 引入端口操作函数// 从setup.s保存的内存区域读取硬件信息
#define ORIG_X              (*(unsigned char *)0x90000)  // 原始X坐标
#define ORIG_Y              (*(unsigned char *)0x90001)  // 原始Y坐标
#define ORIG_VIDEO_PAGE     (*(unsigned short *)0x90004) // 视频页
#define ORIG_VIDEO_MODE     ((*(unsigned short *)0x90006) & 0xff) // 视频模式
#define ORIG_VIDEO_COLS     (((*(unsigned short *)0x90006) & 0xff00) >> 8) // 列数
#define ORIG_VIDEO_LINES    ((*(unsigned short *)0x9000e) & 0xff) // 行数
#define ORIG_VIDEO_EGA_BX   (*(unsigned short *)0x9000a) // EGA显卡参数// 显示器类型定义
#define VIDEO_TYPE_MDA      0x10    /* 单色文本显示器 */
#define VIDEO_TYPE_CGA      0x11    /* CGA彩色显示器 */
#define VIDEO_TYPE_EGAM     0x20    /* EGA/VGA单色模式 */
#define VIDEO_TYPE_EGAC     0x21    /* EGA/VGA彩色模式 */// 静态变量存储控制台参数
static unsigned char    video_type;         /* 显示器类型 */
static unsigned long    video_num_columns;  /* 列数 */
static unsigned long    video_num_lines;    /* 行数 */
static unsigned long    video_mem_base;     /* 显存基地址 */
static unsigned long    video_mem_term;     /* 显存结束地址 */
static unsigned long    video_size_row;     /* 每行字节数 */
static unsigned char    video_page;         /* 视频页 */
static unsigned short   video_port_reg;     /* 视频寄存器选择端口 */
static unsigned short   video_port_val;     /* 视频寄存器值端口 */// 光标位置与屏幕范围控制变量
static unsigned long origin;
static unsigned long scr_end;
static unsigned long pos;
static unsigned long x,y;
static unsigned long top,bottom;/** 定位光标位置函数* 作用:根据坐标计算显存地址并更新光标位置*/
static inline void gotoxy(int new_x, unsigned int new_y)
{ if (new_x > video_num_columns || new_y >= video_num_lines){return;}x = new_x;y = new_y;pos = origin + y * video_size_row + (x << 1);  // 每个字符占2字节(ASCII+属性)
}/** 设置光标位置函数* 作用:通过端口操作将光标移动到计算出的位置*/
static inline void set_cursor()
{cli();  // 关闭中断,确保端口操作原子性outb_p(14, video_port_reg);                   // 发送高8位地址到端口outb_p(0xff & ((pos - video_mem_base) >> 9), video_port_val);outb_p(15, video_port_reg);                   // 发送低8位地址到端口outb_p(0xff & ((pos - video_mem_base) >> 1), video_port_val);sti();  // 恢复中断
}/** 控制台初始化函数:* 1. 读取setup.s保存的硬件信息* 2. 识别显示器类型(MDA/CGA/EGA)* 3. 配置对应的显存地址和端口参数* 4. 初始化光标位置并在屏幕最后一行显示设备标识*/
void con_init(void)
{char *display_desc = "????";  // 显示器类型描述char *display_ptr;            // 显示位置指针// 初始化基础参数video_num_columns = ORIG_VIDEO_COLS;video_size_row = video_num_columns * 2;  // 每个字符占2字节(ASCII+属性)video_num_lines = ORIG_VIDEO_LINES;video_page = ORIG_VIDEO_PAGE;// 判断是否为单色显示器(模式7)if (ORIG_VIDEO_MODE == 7){video_mem_base = 0xb0000;    // 单色显存基地址video_port_reg = 0x3b4;      // 单色模式寄存器端口video_port_val = 0x3b5;      // 单色模式值端口// 区分EGA单色模式与MDAif ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){video_type = VIDEO_TYPE_EGAM;video_mem_term = 0xb8000;  // EGA单色显存结束地址display_desc = "EGAm";     // 标识EGA单色模式}else{video_type = VIDEO_TYPE_MDA;video_mem_term = 0xb2000;  // MDA显存结束地址display_desc = "*MDA";     // 标识MDA显示器}}else  // 彩色显示器{video_mem_base = 0xb8000;    // 彩色显存基地址video_port_reg = 0x3d4;      // 彩色模式寄存器端口video_port_val = 0x3d5;      // 彩色模式值端口// 区分EGA彩色模式与CGAif ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){video_type = VIDEO_TYPE_EGAC;video_mem_term = 0xc0000;  // EGA彩色显存结束地址display_desc = "EGAc";     // 标识EGA彩色模式}else{video_type = VIDEO_TYPE_CGA;video_mem_term = 0xba000;  // CGA显存结束地址display_desc = "*CGA";     // 标识CGA显示器}}// 初始化屏幕范围参数origin = video_mem_base;scr_end = video_mem_base + video_num_lines * video_size_row;top = 0;bottom = video_num_lines;// 恢复原始光标位置并更新硬件光标gotoxy(ORIG_X, ORIG_Y);set_cursor();// 在屏幕最后一行显示显示器类型(方便调试)display_ptr = ((char *)video_mem_base) + video_size_row - 8;while (*display_desc){*display_ptr++ = *display_desc++;  // 写入字符ASCII码display_ptr++;                     // 跳过属性位(默认使用原有属性)}
}

4. 中断控制宏定义(system.h)

提供了中断开关等底层操作宏,保障端口操作的原子性:

c

运行

#ifndef _SYSTEM_H
#define _SYSTEM_H// 中断控制宏定义
#define sti() __asm__("sti"::)  // 开启中断
#define cli() __asm__("cli"::)  // 关闭中断
#define nop() __asm__("nop"::)  // 空操作
#define iret() __asm__("iret"::) // 中断返回#endif

二、编译系统设计

控制台初始化代码的编译依赖于多层次的 Makefile 系统,确保各模块按正确顺序和参数编译:

1. 根目录 Makefile

makefile

AS := as
LD := ld -m elf_x86_64
LDFLAG := -Ttext 0x0 -s --oformat binaryall : linux.img  # 最终目标:生成可启动镜像# 拼接引导程序、设置程序和内核系统
linux.img : tools/build bootsect setup kernel/system./tools/build bootsect setup kernel/system > $@# 编译构建工具
tools/build : tools/build.cgcc -o $@ $<# 编译内核系统
kernel/system : kernel/head.scd kernel; make system; cd ..# 编译引导扇区
bootsect : bootsect.o$(LD) $(LDFLAG) -o $@ $<
bootsect.o : bootsect.s$(AS) -o $@ $<# 编译设置程序
setup : setup.o$(LD) $(LDFLAG) -e _start_setup -o $@ $<
setup.o : setup.s$(AS) -o $@ $<# 清理编译产物
clean:rm -f *.o bootsect setup tools/build linux.imgcd kernel; make clean; cd ..cd tools; make clean; cd ..# 运行模拟器
run:qemu-system-i386 -fda linux.img  # 使用qemu模拟x86环境

2. 内核目录 Makefile

makefile

# ---------- kernel/Makefile ----------
AS      := as
CC      := gcc
LD      := ld
OBJCOPY := objcopy
ASFLAGS := --32  # 32位汇编
CFLAGS  := -m32 -march=i386 -I../include -nostdinc -Wall \-fno-builtin -fno-stack-protector -fno-pic -fno-pie  # 禁用标准库和安全特性OBJS    := head.o main.o sched.o chr_drv/chr_drv.a  # 内核组件all: system  # 目标:生成内核系统文件# 链接内核组件
system: $(OBJS)$(LD) -m elf_i386 -Ttext 0x0 -e startup_32 -nmagic -o system.elf $(OBJS)$(OBJCOPY) -O binary system.elf system  # 转换为二进制镜像# 编译各模块
head.o: head.s$(AS) $(ASFLAGS) -o $@ $<
main.o: main.c$(CC) $(CFLAGS) -c -o $@ $<
sched.o: sched.c$(CC) $(CFLAGS) -c -o $@ $<
chr_drv/chr_drv.a: chr_drv/*.c  # 字符设备驱动库cd chr_drv; make chr_drv.a; cd ..# 清理内核编译产物
clean:rm -f *.o system system.elfcd chr_drv; make clean; cd ..

3. 字符设备目录 Makefile

makefile

# ---------- kernel/chr_drv/Makefile ----------
CC      := gcc
AR      := ar
CFLAGS  := -m32 -I../../include -nostdinc -Wall -fomit-frame-pointerOBJS    := tty_io.o console.o  # 终端与控制台目标文件chr_drv.a: $(OBJS)$(AR) rcs $@ $(OBJS)  # 创建静态链接库# 编译各目标文件
tty_io.o: tty_io.c$(CC) $(CFLAGS) -c -o $@ $<
console.o: console.c$(CC) $(CFLAGS) -c -o $@ $<# 清理编译产物
clean:rm -f *.o chr_drv.a

三、核心技术解析

1. 硬件信息获取机制

控制台初始化依赖于启动阶段保存的硬件信息,这些信息通过内存映射方式直接访问:

  • 0x90000 开始的内存区域由 setup.s 程序预先填充 BIOS 提供的硬件参数
  • 通过宏定义直接解析该区域数据,获取视频模式、行列数等关键信息
  • 避免了初始化阶段频繁调用 BIOS 中断,提高启动效率

2. 多显示器适配策略

代码通过分层判断实现对不同显示器的兼容:

  1. 模式判断:通过 ORIG_VIDEO_MODE 区分单色(7)与彩色模式
  2. 端口配置:单色模式使用 0x3b4/0x3b5 端口,彩色模式使用 0x3d4/0x3d5 端口
  3. 显存划分:不同显示器的显存范围不同(MDA:0xb0000-0xb2000,EGA 彩色:0xb8000-0xc0000)
  4. 类型标识:通过 ORIG_VIDEO_EGA_BX 区分 EGA 与传统 MDA/CGA 设备

3. 光标控制实现

光标位置控制通过硬件端口操作完成,分为两个关键步骤:

  • 地址计算:gotoxy () 函数根据坐标计算字符在显存中的位置(每个字符占 2 字节)
  • 端口操作:set_cursor () 函数通过 outb_p () 向视频控制器发送光标位置指令
  • 原子性保障:通过 cli ()/sti () 关闭 / 开启中断,确保端口操作不被打断

4. 端口操作设计

端口操作宏函数采用嵌入式汇编实现,具有以下特点:

  • 使用寄存器约束符("a"、"d")自动分配寄存器,避免冲突
  • 带延迟版本(outb_p/inb_p)通过空跳转指令提供硬件响应时间
  • volatile 关键字确保端口操作不被编译器优化省略
  • 统一封装硬件指令,简化上层代码对硬件的直接操作

四、运行与验证流程

  1. 编译镜像:执行 make 命令,通过嵌套 Makefile 系统生成 linux.img
  2. 启动模拟器:执行 make run 启动 QEMU 模拟器
  3. 验证结果
    • 屏幕最后一行显示显示器类型标识(如 "EGAm"、"*CGA")
    • 光标定位到 BIOS 保存的原始位置
    • 无硬件错误或显示异常

控制台初始化作为内核与硬件交互的早期环节,展示了 Linux 0.12 内核 "直接操作硬件、精简高效" 的设计哲学。从端口操作到显示器适配,每一处实现都体现了对硬件特性的深刻理解,为后续内核功能提供了基础的输入输出平台。

http://www.dtcms.com/a/346235.html

相关文章:

  • 商密保卫战:保密性认定的司法迷局与破局之道
  • 记录一下面试题:找字符串中第一次出现1次的字符
  • Kubernetes配置与密钥管理及存储体系实战指南
  • Adobe Illustrator默认键盘快捷键
  • 嵌入式开发中,usb通信中输出端点和输入端点
  • AP服务发现PRS_SOMEIPSD_00255 的解析
  • Java面试-访问修饰符:public、protected、default、private 详解
  • CAN总线工具学习:DBC解析、设备扫描与报文监控
  • Linux环境搭建FTP协议
  • fdisk工具源码编译生成
  • 记SpringBoot3.x + SpringSecurity6.x的实现
  • 20250822日记
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第四章知识点问答(37题)
  • 如何编译botan加密库?
  • 模板商城探秘:DINO-X 定制模板指南(1)
  • Ansys Motor-CAD:概述(EMag、THERM、LAB、MECH)
  • Unreal Engine UActorComponent
  • 豆包 + 蘑兔,破解写歌难题!
  • 普中烧录软件 PZISP,打不开,提示“应用程序无法启动,因为应用程序并行配置不正确.....”
  • 深度学习设计模式:责任链(Chain of Responsibility)模式(例子+业务场景+八股)
  • RFID技术在铸管生产车间AGV小车的使用
  • SQL 复杂连接与嵌套查询的优化之道:从自连接、不等值连接到 CTE 的体系化实践
  • 「数据获取」《中国农村统计年鉴》1985-2024(获取方式看绑定的资源)
  • Python中各种数据类型的常用方法
  • 国产轻量级桌面GIS软件Snaplayers从入门到精通(20)
  • 自定义单线通信协议解析
  • Unreal Engine Simulate Physics
  • MySQL InnoDB记录存储结构深度解析
  • windows 帮我写一个nginx的配置,端口是9999,静态资源的路径是D:\upload
  • 企业架构之微服务应用架构