设置早期打印功能setup_early_printk
c
int __init setup_early_printk(char *opt)
{
char *space;
char buf[256];
if (early_console_initialized)
return -1;
opt = strchr(opt, '=') + 1;
strlcpy(buf,opt,sizeof(buf));
space = strchr(buf, ' ');
if (space)
*space = 0;
if (strstr(buf,"keep"))
keep_early = 1;
if (!strncmp(buf, "serial", 6)) {
early_serial_init(buf + 6);
early_console = &early_serial_console;
} else if (!strncmp(buf, "ttyS", 4)) {
early_serial_init(buf);
early_console = &early_serial_console;
} else if (!strncmp(buf, "vga", 3)) {
early_console = &early_vga_console;
} else {
early_console = NULL;
return -1;
}
early_console_initialized = 1;
register_console(early_console);
return 0;
}
1. 函数功能概述
这是一个用于设置早期打印功能的函数,在内核启动初期就提供控制台输出能力,便于调试启动过程中的问题
2. 代码分段详解
2.1. 第一段:变量声明和初始检查
c
int __init setup_early_printk(char *opt)
{
char *space;
char buf[256];
if (early_console_initialized)
return -1;
__init宏表示这个函数只在初始化阶段使用,之后内存会被释放char *space和char buf[256]声明局部变量,buf用于存储处理后的选项字符串if (early_console_initialized)检查全局标志,如果早期控制台已经初始化过,直接返回 -1 避免重复初始化
2.2. 第二段:参数解析
c
opt = strchr(opt, '=') + 1;
strlcpy(buf,opt,sizeof(buf));
space = strchr(buf, ' ');
if (space)
*space = 0;
strchr(opt, '=') + 1查找参数中的等号,并指向等号后的内容(跳过参数名)strlcpy(buf,opt,sizeof(buf))安全地复制选项字符串到缓冲区strchr(buf, ' ')查找空格位置(如果有其他参数)if (space) *space = 0如果有空格,将其替换为字符串结束符,截断后续参数
2.3. 第三段:keep 选项处理
c
if (strstr(buf,"keep"))
keep_early = 1;
strstr(buf,"keep")检查是否包含 "keep" 子串- 如果找到 "keep",设置
keep_early = 1,表示保留早期控制台不被后续的正常控制台替换
2.4. 第四段:串口控制台设置
c
if (!strncmp(buf, "serial", 6)) {
early_serial_init(buf + 6);
early_console = &early_serial_console;
} else if (!strncmp(buf, "ttyS", 4)) {
early_serial_init(buf);
early_console = &early_serial_console;
!strncmp(buf, "serial", 6)检查是否以 "serial" 开头- 如果是,调用
early_serial_init(buf + 6)初始化串口,跳过 "serial" 前缀 !strncmp(buf, "ttyS", 4)检查是否以 "ttyS" 开头(传统串口设备名)- 两种情况下都设置
early_console指向串口控制台结构体
2.5. 第五段:VGA 控制台和其他情况
c
} else if (!strncmp(buf, "vga", 3)) {
early_console = &early_vga_console;
} else {
early_console = NULL;
return -1;
}
!strncmp(buf, "vga", 3)检查是否以 "vga" 开头- 如果是,设置
early_console指向 VGA 控制台结构体 - 如果都不匹配,设置
early_console为 NULL 并返回 -1 表示失败
2.6. 第六段:完成初始化
c
early_console_initialized = 1;
register_console(early_console);
return 0;
}
early_console_initialized = 1设置全局标志表示早期控制台已初始化register_console(early_console)向内核注册这个控制台return 0返回成功状态
3. 函数功能总结
主要功能:根据启动参数配置和初始化早期控制台,在内核启动初期提供调试输出能力
支持的控制台类型:
- 串口控制台("serial" 或 "
ttyS" 开头) - VGA 控制台("
vga" 开头)
参数格式示例:
earlyprintk=serial,0x3f8,115200earlyprintk=ttyS0,115200earlyprintk=vgaearlyprintk=serial,keep(保留早期控制台)
返回值:
- 0:成功
- -1:失败(已初始化或无效参数)
这个函数在内核启动早期阶段至关重要,为后续的启动过程提供了可靠的输出和调试通道
初始化早期串口控制台early_serial_init
c
static __init void early_serial_init(char *s)
{
unsigned char c;
unsigned divisor;
unsigned baud = DEFAULT_BAUD;
char *e;
if (*s == ',')
++s;
if (*s) {
unsigned port;
if (!strncmp(s,"0x",2)) {
early_serial_base = simple_strtoul(s, &e, 16);
} else {
static int bases[] = { 0x3f8, 0x2f8 };
if (!strncmp(s,"ttyS",4))
s += 4;
port = simple_strtoul(s, &e, 10);
if (port > 1 || s == e)
port = 0;
early_serial_base = bases[port];
}
s += strcspn(s, ",");
if (*s == ',')
s++;
}
outb(0x3, early_serial_base + LCR); /* 8n1 */
outb(0, early_serial_base + IER); /* no interrupt */
outb(0, early_serial_base + FCR); /* no fifo */
outb(0x3, early_serial_base + MCR); /* DTR + RTS */
if (*s) {
baud = simple_strtoul(s, &e, 0);
if (baud == 0 || s == e)
baud = DEFAULT_BAUD;
}
divisor = 115200 / baud;
c = inb(early_serial_base + LCR);
outb(c | DLAB, early_serial_base + LCR);
outb(divisor & 0xff, early_serial_base + DLL);
outb((divisor >> 8) & 0xff, early_serial_base + DLH);
outb(c & ~DLAB, early_serial_base + LCR);
}
1. 函数功能概述
这是一个用于初始化早期串口控制台的函数,在内核启动初期设置串口参数
1.1. 第一段:变量声明和初始设置
c
static __init void early_serial_init(char *s)
{
unsigned char c;
unsigned divisor;
unsigned baud = DEFAULT_BAUD;
char *e;
static __init:静态函数,初始化后内存可释放c:用于临时存储LCR(线路控制寄存器)值divisor:波特率分频器baud:波特率,默认DEFAULT_BAUD(通常是9600)e:字符串转换结束指针
1.2. 第二段:参数解析 - 跳过前导逗号
c
if (*s == ',')
++s;
- 如果参数字符串以逗号开头,跳过这个逗号
- 处理格式如:
,0x3f8,115200
1.3. 第三段:解析串口基地址 - 十六进制格式
c
if (*s) {
unsigned port;
if (!strncmp(s,"0x",2)) {
early_serial_base = simple_strtoul(s, &e, 16);
}
- 检查字符串非空
- 如果以"0x"开头,按十六进制解析串口基地址
simple_strtoul:简单字符串转无符号长整型- 例如:
0x3f8→ 串口1的标准地址
1.4. 第四段:解析串口基地址 - 十进制或设备名格式
c
else {
static int bases[] = { 0x3f8, 0x2f8 };
if (!strncmp(s,"ttyS",4))
s += 4;
port = simple_strtoul(s, &e, 10);
if (port > 1 || s == e)
port = 0;
early_serial_base = bases[port];
}
bases[]:预定义的串口基地址数组0x3f8:COM10x2f8:COM2
- 如果以"
ttyS"开头,跳过前4个字符 - 按十进制解析端口号(0或1)
- 如果端口号无效(>1或解析失败),默认使用端口0
- 根据端口号选择对应的基地址
1.5. 第五段:移动到波特率参数
c
s += strcspn(s, ",");
if (*s == ',')
s++;
}
strcspn(s, ","):查找下一个逗号的位置- 移动指针到逗号位置,然后跳过逗号
- 准备解析波特率参数
1.6. 第六段:串口基本配置 - 8N1格式
c
outb(0x3, early_serial_base + LCR); /* 8n1 */
LCR:线路控制寄存器0x3= 0b00000011:8位数据,无校验,1位停止位(8N1)- 设置串口数据帧格式
1.7. 第七段:禁用中断
c
outb(0, early_serial_base + IER); /* no interrupt */
IER:中断使能寄存器0:禁用所有中断(早期启动不需要中断)
1.8. 第八段:禁用FIFO
c
outb(0, early_serial_base + FCR); /* no fifo */
FCR:FIFO控制寄存器0:禁用FIFO缓冲区
1.9. 第九段:设置调制解调器控制
c
outb(0x3, early_serial_base + MCR); /* DTR + RTS */
MCR:调制解调器控制寄存器0x3= 0b00000011:设置DTR和RTS信号线
1.10. 第十段:解析波特率参数
c
if (*s) {
baud = simple_strtoul(s, &e, 0);
if (baud == 0 || s == e)
baud = DEFAULT_BAUD;
}
- 如果还有参数字符串,解析波特率
simple_strtoul(s, &e, 0):自动检测进制(十进制/十六进制)- 如果解析失败或为0,使用默认波特率
1.11. 第十一段:计算波特率分频器
c
divisor = 115200 / baud;
- 计算分频值:基准频率115200Hz / 目标波特率
1.12. 第十二段:进入分频器设置模式
c
c = inb(early_serial_base + LCR);
outb(c | DLAB, early_serial_base + LCR);
- 读取当前LCR值
c | DLAB:设置DLAB位- 进入分频器编程模式
1.13. 第十三段:设置分频器低字节和高字节
c
outb(divisor & 0xff, early_serial_base + DLL);
outb((divisor >> 8) & 0xff, early_serial_base + DLH);
DLL:分频器锁存低字节DLH:分频器锁存高字节- 分别设置分频器的低8位和高8位
1.14. 第十四段:退出分频器设置模式
c
outb(c & ~DLAB, early_serial_base + LCR);
}
c & ~DLAB:清除DLAB位- 恢复正常操作模式,分频器开始生效
2. 串口寄存器说明
| 寄存器 | 偏移 | 功能 |
|---|---|---|
| DLL | 0x0 | 分频器锁存低字节(DLAB=1时) |
| DLH | 0x1 | 分频器锁存高字节(DLAB=1时) |
| IER | 0x1 | 中断使能寄存器(DLAB=0时) |
| FCR | 0x2 | FIFO控制寄存器 |
| LCR | 0x3 | 线路控制寄存器 |
| MCR | 0x4 | 调制解调器控制寄存器 |
3. 支持的参数格式示例
- 十六进制地址 :
0x3f8,115200 - 设备名 :
ttyS0,9600或ttyS1,57600 - 数字端口 :
0,115200或1,19200 - 默认配置 :空字符串或
,使用COM1和9600波特率
向内核注册一个控制台设备register_console
c
void register_console(struct console * console)
{
int i;
unsigned long flags;
if (preferred_console < 0)
preferred_console = selected_console;
/*
* See if we want to use this console driver. If we
* didn't select a console we take the first one
* that registers here.
*/
if (preferred_console < 0) {
if (console->index < 0)
console->index = 0;
if (console->setup == NULL ||
console->setup(console, NULL) == 0) {
console->flags |= CON_ENABLED | CON_CONSDEV;
preferred_console = 0;
}
}
/*
* See if this console matches one we selected on
* the command line.
*/
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
if (strcmp(console_cmdline[i].name, console->name) != 0)
continue;
if (console->index >= 0 &&
console->index != console_cmdline[i].index)
continue;
if (console->index < 0)
console->index = console_cmdline[i].index;
if (console->setup &&
console->setup(console, console_cmdline[i].options) != 0)
break;
console->flags |= CON_ENABLED;
console->index = console_cmdline[i].index;
if (i == preferred_console)
console->flags |= CON_CONSDEV;
break;
}
if (!(console->flags & CON_ENABLED))
return;
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
acquire_console_sem();
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
} else {
console->next = console_drivers->next;
console_drivers->next = console;
}
if (console->flags & CON_PRINTBUFFER) {
/*
* release_console_sem() will print out the buffered messages
* for us.
*/
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
}
1. 函数功能概述
这个函数用于向内核注册一个控制台设备,是内核控制台子系统的核心注册函数
1.1. 第一段:变量声明和初始检查
c
void register_console(struct console * console)
{
int i;
unsigned long flags;
if (preferred_console < 0)
preferred_console = selected_console;
i:循环计数器flags:用于保存中断状态的变量preferred_console:首选控制台索引selected_console:已选控制台索引- 如果首选控制台未设置,使用已选控制台
1.2. 第二段:自动选择第一个可用控制台
c
if (preferred_console < 0) {
if (console->index < 0)
console->index = 0;
if (console->setup == NULL ||
console->setup(console, NULL) == 0) {
console->flags |= CON_ENABLED | CON_CONSDEV;
preferred_console = 0;
}
}
- 如果仍然没有首选控制台:
- 如果控制台索引为负,设为0
- 如果控制台不需要setup或setup成功:
- 设置
CON_ENABLED(启用)和CON_CONSDEV(控制台设备)标志 - 设置首选控制台为0
- 设置
1.3. 第三段:检查命令行匹配的控制台
c
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
if (strcmp(console_cmdline[i].name, console->name) != 0)
continue;
- 遍历命令行中指定的控制台配置
- 比较控制台名称,不匹配则继续循环
1.4. 第四段:索引检查和设置
c
if (console->index >= 0 &&
console->index != console_cmdline[i].index)
continue;
if (console->index < 0)
console->index = console_cmdline[i].index;
- 如果控制台索引已设置且不匹配,跳过
- 如果控制台索引未设置,使用命令行中的索引
1.5. 第五段:控制台初始化和启用
c
if (console->setup &&
console->setup(console, console_cmdline[i].options) != 0)
break;
console->flags |= CON_ENABLED;
console->index = console_cmdline[i].index;
- 如果控制台有setup函数,调用它进行初始化
- 如果setup失败,跳出循环
- 设置
CON_ENABLED标志 - 设置控制台索引
1.6. 第六段:设置首选控制台标志
c
if (i == preferred_console)
console->flags |= CON_CONSDEV;
break;
}
- 如果当前索引是首选控制台,设置
CON_CONSDEV标志 - 跳出循环(找到一个匹配的就够了)
1.7. 第七段:检查控制台是否启用
c
if (!(console->flags & CON_ENABLED))
return;
- 如果控制台没有被启用,直接返回
- 这是函数的早期退出点
1.8. 第八段:获取控制台信号量
c
acquire_console_sem();
- 获取控制台信号量,保护控制台链表操作
- 防止并发修改控制台驱动程序链表
1.9. 第九段:插入控制台到链表头部
c
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
}
- 如果是控制台设备或链表为空:
- 将新控制台插入链表头部
- 更新全局
console_drivers指针
1.10. 第十段:插入控制台到链表第二个位置
c
else {
console->next = console_drivers->next;
console_drivers->next = console;
}
- 如果不是首选控制台且链表不为空:
- 插入到链表第二个位置
- 保持首选控制台在头部
1.11. 第十一段:处理打印缓冲区
c
if (console->flags & CON_PRINTBUFFER) {
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
- 如果设置了
CON_PRINTBUFFER标志:- 保存中断状态并获取日志缓冲区锁
- 设置控制台起始位置为日志起始位置
- 恢复中断状态并释放锁
- 这允许新控制台输出之前的缓冲消息
1.12. 第十二段:释放信号量并返回
c
release_console_sem();
}
- 释放控制台信号量
- 函数结束
2. 关键标志位说明
| 标志 | 含义 |
|---|---|
CON_ENABLED |
控制台已启用,可以接收输出 |
CON_CONSDEV |
这是首选的控制台设备 |
CON_PRINTBUFFER |
应该输出之前的缓冲消息 |
3. 函数功能总结
主要功能:向内核注册一个控制台驱动程序,并按照优先级将其插入到控制台链表中。
处理逻辑:
- 自动选择:如果没有指定首选控制台,选择第一个可用的
- 命令行匹配:根据内核命令行参数匹配和配置控制台
- 链表管理:维护一个有序的控制台驱动程序链表
- 缓冲区重放:可选地输出启动早期的缓冲消息
关键特性:
- 线程安全的链表操作
- 灵活的控制台选择机制
- 支持多个控制台同时运行
- 保持首选控制台在输出链的头部
这个函数是Linux内核控制台子系统的核心,负责管理所有控制台驱动程序的注册和优先级排序
找到目标字符串的位置strcspn
c
size_t strcspn(const char *s, const char *reject)
{
const char *p;
const char *r;
size_t count = 0;
for (p = s; *p != '\0'; ++p) {
for (r = reject; *r != '\0'; ++r) {
if (*p == *r)
return count;
}
++count;
}
return count;
}
1. 函数功能概述
strcspn 函数计算字符串 s 开头连续不包含 reject 中任何字符的字符个数,如果reject字符串只有一个字符,就是找到源字符串中reject字符的第一个位置
1.1. 第一段:函数声明和变量定义
c
size_t strcspn(const char *s, const char *reject)
{
const char *p;
const char *r;
size_t count = 0;
size_t:无符号整数类型,用于表示大小和计数const char *s:要扫描的源字符串const char *reject:包含要拒绝的字符集合p:用于遍历源字符串s的指针r:用于遍历拒绝字符集reject的指针count:计数器,记录连续不包含拒绝字符的字符数
1.2. 第二段:外层循环 - 遍历源字符串
c
for (p = s; *p != '\0'; ++p) {
p = s:初始化指针p指向源字符串开头*p != '\0':循环条件,直到遇到字符串结束符++p:每次循环后指针向后移动一个字符- 这个循环逐个检查源字符串中的每个字符
1.3. 第三段:内层循环 - 检查拒绝字符集
c
for (r = reject; *r != '\0'; ++r) {
r = reject:初始化指针r指向拒绝字符集开头*r != '\0':循环条件,直到拒绝字符集结束++r:每次循环后指针向后移动一个字符- 这个循环检查当前源字符是否在拒绝字符集中
1.4. 第四段:字符匹配检查
c
if (*p == *r)
return count;
*p == *r:比较当前源字符*p和当前拒绝字符*r- 如果相等,表示当前源字符在拒绝字符集中
return count:立即返回当前计数(不包含这个匹配的字符)
1.5. 第五段:计数递增
c
++count;
}
- 只有在内层循环完整执行(当前字符不在拒绝字符集中)时才会执行
++count:增加计数器
1.6. 第六段:处理整个字符串都有效的情况
c
return count;
}
- 如果整个源字符串都不包含任何拒绝字符,返回总计数
- 此时
count等于源字符串的长度
2. 函数功能总结
主要功能:计算字符串开头连续不包含指定字符集中任何字符的字符个数。
返回值含义:
- 返回从字符串开头到第一个拒绝字符之前的字符数量
- 如果字符串开头没有拒绝字符,返回字符串长度
典型应用场景:
- 字符串分割:找到第一个分隔符的位置
- 词法分析:识别标识符或数字的起始部分