linux kernel下输入输出console如何实现

本文详细探讨了Linux Kernel如何实现输入输出console,包括指定kernel调试console、kernel中printk的选择、kernel中console的注册以及user空间console的选择。通过分析不同阶段的处理流程和关键函数,阐述了console在kernel和user空间的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近工作在调试usb虚拟串口,让其作为kernel启动的调试串口,以及user空间的输入输出控制台。

利用这个机会,学习下printk如何选择往哪个console输出以及user空间下控制台如何选择,记录与此,与大家共享,也方便自己以后翻阅。

Kernel版本号:3.4.55

依照我的思路(还是时间顺序)分了4部分,指定kernel调试console ,  kernel下printk console的选择 ,kernel下console的注册,user空间console的选择。


一 指定kernel调试console

首先看kernel启动时如何获取和处理指定的console参数。

kernel的启动参数cmdline可以指定调试console,如指定‘console=ttyS0,115200’,

kernel如何解析cmdline,我之前写了一篇博文如下:

http://blog.youkuaiyun.com/skyflying2012/article/details/41142801

根据之前的分析,cmdline中有console=xxx,start_kernel中parse_args遍历.init.setup段所有obs_kernel_param。

kernel/printk.c中注册了‘console=’的解析函数console_setup(注册了obs_kernel_param),所以匹配成功,会调用console_setup来解析,如下:

static int __init console_setup(char *str)
{
    char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
    char *s, *options, *brl_options = NULL;
    int idx; 

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    if (!memcmp(str, "brl,", 4)) {
        brl_options = "";
        str += 4;
    } else if (!memcmp(str, "brl=", 4)) {
        brl_options = str + 4; 
        str = strchr(brl_options, ',');
        if (!str) {
            printk(KERN_ERR "need port name after brl=\n");
            return 1;
        }    
        *(str++) = 0; 
    }    
#endif

    /*
     * Decode str into name, index, options.
     */
    if (str[0] >= '0' && str[0] <= '9') {
        strcpy(buf, "ttyS");
        strncpy(buf + 4, str, sizeof(buf) - 5);
    } else {
        strncpy(buf, str, sizeof(buf) - 1);
    }
    buf[sizeof(buf) - 1] = 0;
    if ((options = strchr(str, ',')) != NULL)
        *(options++) = 0;
#ifdef __sparc__
    if (!strcmp(str, "ttya"))
        strcpy(buf, "ttyS0");
    if (!strcmp(str, "ttyb"))
        strcpy(buf, "ttyS1");
#endif
    for (s = buf; *s; s++)
        if ((*s >= '0' && *s <= '9') || *s == ',')
            break;
    idx = simple_strtoul(s, NULL, 10);
    *s = 0;

    __add_preferred_console(buf, idx, options, brl_options);
    console_set_on_cmdline = 1;
    return 1;
}
__setup("console=", console_setup);
 

参数是console=的值字符串,如“ttyS0,115200”,console_setup对console=参数值做解析,以ttyS0,115200为例,最后buf=“ttyS”,idx=0,options="115200",brl_options=NULL。调用__add_preferred_console如下:

/*
 * If exclusive_console is non-NULL then only this console is to be printed to.
 */
static struct console *exclusive_console;

/*
 *  Array of consoles built from command line options (console=)
 */
struct console_cmdline
{                  
    char    name[8];            /* Name of the driver       */
    int index;              /* Minor dev. to use        */
    char    *options;           /* Options for the driver   */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    char    *brl_options;           /* Options for braille driver */
#endif
};

#define MAX_CMDLINECONSOLES 8
        
static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
static int selected_console = -1;
static int preferred_console = -1;
int console_set_on_cmdline;
EXPORT_SYMBOL(console_set_on_cmdline);
static int __add_preferred_console(char *name, int idx, char *options,
                   char *brl_options)
{
    struct console_cmdline *c;
    int i;

    /*
     *  See if this tty is not yet registered, and
     *  if we have a slot free.
     */
    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
        if (strcmp(console_cmdline[i].name, name) == 0 &&
              console_cmdline[i].index == idx) {
                if (!brl_options)
                    selected_console = i;
                return 0;
        }
    if (i == MAX_CMDLINECONSOLES)
        return -E2BIG;
    if (!brl_options)
        selected_console = i;
    c = &console_cmdline[i];
    strlcpy(c->name, name, sizeof(c->name));
    c->options = options;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    c->brl_options = brl_options;
#endif
    c->index = idx;
    return 0;
}

 

kernel利用结构体数组console_cmdline[8],最多可支持8个cmdline传入的console参数。

__add_preferred_console将name idx options保存到数组下一个成员console_cmdline结构体中,如果数组中已有重名,则不添加,并置selected_console为最新添加的console_cmdline的下标号。

比如cmdline中有“console=ttyS0,115200 console=ttyS1,9600”

则在console_cmdline[8]数组中console_cmdline[0]代表ttyS0,console_cmdline[1]代表ttyS1,而selected_console=1.

<think>好的,我现在需要回答用户的问题:“内核拉起的应用程序标准输入输出不是指向console吗”。首先,我要理解用户的问题核心在哪里。用户似乎在问,当内核启动一个应用程序时,它的标准输入、输出和错误是否默认指向控制台console)。如果是的话,为什么有时候会有不同的表现?或者用户可能遇到了相关的问题,比如应用程序输出没有显示在控制台上,想了解背后的机制。 首先,我需要回顾Linux系统中进程的标准输入输出(stdin, stdout, stderr)的默认行为。通常,当用户通过shell启动一个程序时,标准输入输出会连接到终端设备,例如/dev/ttyS0(串口终端)或/dev/console(系统控制台)。但如果是内核直接启动的进程,比如通过内核线程或早期的init进程,情况可能有所不同。 接下来,我需要考虑内核启动的应用程序指的是什么。例如,在系统启动过程中,内核会启动init进程(如systemd或SysVinit),这个进程的父进程是内核(PID 1)。此时,init进程的标准输入输出是如何设置的?另外,内核本身在启动过程中可能会有打印信息输出控制台,这些信息是通过printk函数处理的,而printk输出通常指向console,这可能与用户进程的标准输入输出不同。 然后,我需要区分用户空间应用程序和内核空间的功能。用户空间的应用程序的标准输入输出是由C库(如glibc)处理的,这些库函数最终会通过系统调用(如read、write)与文件描述符(0,1,2)交互。而内核的printk消息是直接通过注册的console驱动输出,不经过用户空间的文件描述符机制。因此,当用户说“内核拉起的应用程序”时,需要明确这个应用程序是运行在用户空间还是内核空间。 另外,需要考虑系统启动过程中不同阶段的控制台设置。在早期启动阶段,内核可能使用简单的输出方式,比如串口或VGA文本模式,而当用户空间初始化后,控制台可能被重定向到其他设备,比如图形终端或虚拟终端(如tty1)。这时候,用户空间进程的标准输入输出可能指向不同的终端设备。 可能还需要讨论/dev/console的特殊性。在Linux系统中,/dev/console是一个特殊的设备文件,通常指向当前活动的控制台。内核参数中的console=参数可以指定多个控制台设备,内核会将消息发送到所有指定的控制台。但是用户进程的标准输入输出是否默认指向/dev/console呢?这可能需要查看进程的环境和打开的文件描述符。 另外,用户可能混淆了内核的console和用户进程的标准输入输出。例如,当系统启动后,用户登录到tty1终端,那么该终端会话中的进程的标准输入输出会连接到tty1的设备文件(如/dev/tty1),而不是内核的console设备。而内核的printk消息可能同时输出到多个console设备,包括串口和tty1,这取决于内核参数的配置。 还需要考虑init进程的标准输入输出的设置。例如,在systemd系统中,PID 1的进程的标准输出可能会被重定向到日志系统(如journald),而不是直接显示在控制台上。这可能让用户觉得标准输出没有指向console,但实际上是因为被中间层处理了。 可能存在的误区是,用户认为所有由内核启动的进程(包括用户空间的init进程)的标准输入输出都会自动连接到console设备,但实际上这取决于进程自身是如何设置文件描述符的。例如,在启动脚本中,可能会将标准输出重定向到文件或其他设备。 总结起来,回答这个问题需要分几个部分: 1. 用户空间进程的标准输入输出默认指向什么设备? 2. 内核启动的进程(如init)如何设置其标准输入输出? 3. 内核的printk输出和用户进程的标准输出有何不同? 4. /dev/console的作用和配置。 5. 系统启动过程中控制台的变化和重定向。 需要确保回答结构清晰,逐步引导用户理解这些概念,同时提供具体的例子和可能的配置方式,帮助用户理解不同情况下标准输入输出的指向。此外,需要验证一些关键点,比如默认情况下用户进程的标准输入输出是否确实指向当前终端,以及内核参数如何影响console设备的选择。</think>### 分步解析与回答 在Linux系统中,内核启动的应用程序的标准输入输出(stdin/stdout/stderr)是否指向控制台console),取决于**进程的创建方式**和**系统配置**。以下是关键机制的分步说明: --- ### 一、基本概念区分 1. **内核控制台Kernel Console)** - 通过`printk`输出内核日志,直接操作硬件(如UART、VGA),由`CONFIG_CONSOLE`相关配置和启动参数`console=`指定。 - **不依赖用户态进程**,即使系统未初始化完成仍可输出。 2. **用户态控制台(User Console)** - 用户空间进程的标准输入输出通常连接到终端设备(如`/dev/ttyS0`、`/dev/tty1`),由Shell或初始化系统管理。 --- ### 二、内核启动的应用程序类型 #### 1. 内核线程(Kernel Thread) - **无标准输入输出**:内核线程运行在内核空间,不关联终端设备,例如`kworker`、`ksoftirqd`。 - **日志输出**:通过`printk`写入内核日志缓冲区(`dmesg`可查看),而非用户态控制台。 #### 2. 用户态初始化进程(如`init`) - **标准流的默认行为**: 由内核启动的第一个用户进程(PID 1,如`systemd`或`init`)的标准输入输出**默认继承自内核**,但实际行为受以下因素影响: - **启动参数控制**:通过内核参数`init=`指定初始化程序,其标准流可能重定向。 - **初始化配置**:例如`systemd`默认将标准输出重定向到日志系统(`journald`),而非直接输出控制台。 --- ### 三、关键机制详解 #### 1. 标准流的初始赋值 - **进程创建时的默认行为** 当内核通过`execve`系统调用启动用户程序时,子进程会继承父进程的文件描述符表。 - 若父进程是内核(如`init`进程),其标准输入输出默认指向**内核的原始控制台设备**(如`/dev/console`)。 - 但大多数初始化系统(如`systemd`)会主动重置标准流,将其绑定到特定终端或日志服务。 - **验证方法** 通过检查进程的文件描述符符号链接: ```bash ls -l /proc/1/fd/0 /proc/1/fd/1 /proc/1/fd/2 # 查看PID 1的标准流指向 ``` 典型输出示例: ``` lrwx------. 1 root root 64 May 10 09:00 /proc/1/fd/0 -> /dev/null lrwx------. 1 root root 64 May 10 09:00 /proc/1/fd/1 -> /dev/null lrwx------. 1 root root 64 May 10 09:00 /proc/1/fd/2 -> /dev/null ``` 此例中,`systemd`将标准流重定向到`/dev/null`,避免直接操作控制台。 #### 2. 控制台设备与终端的关系 - **`/dev/console`的特殊性** - 是内核的“主控制台”设备,通常指向启动参数中第一个`console=`指定的设备(如`ttyS0`)。 - 用户进程可通过打开`/dev/console`直接读写内核控制台,但需具备权限(如`CAP_SYS_ADMIN`)。 - **用户终端的动态分配** - 登录后的Shell进程标准流通常连接到虚拟终端(如`/dev/tty1`)或伪终端(如`/dev/pts/0`),而非`/dev/console`。 - 用户进程的标准流继承自启动它的Shell,因此与控制台解耦。 --- ### 四、配置与调试技巧 #### 1. 强制标准流指向控制台 - **内核启动参数** 在启动参数中指定`init`程序的标准流: ```bash console=ttyS0,115200 init=/bin/sh -c "exec /sbin/init >/dev/console 2>&1" ``` 此配置将`init`进程的标准输出和错误重定向到`/dev/console`。 - **初始化脚本调整** 修改初始化脚本(如`/etc/inittab`或`systemd.service`文件),显式重定向标准流: ```bash # 在SysVinit系统中 ::respawn:/bin/myapp >/dev/console 2>&1 ``` #### 2. 诊断标准流指向 - **查看进程文件描述符** ```bash ls -l /proc/<PID>/fd/{0,1,2} # 替换<PID>为目标进程的ID ``` - **使用`strace`跟踪系统调用** ```bash strace -e trace=openat,dup2,write -p <PID> # 观察文件打开和输出行为 ``` --- ### 五、常见误解澄清 #### 1. “内核启动的进程默认使用`printk`的输出通道” - **错误**:`printk`是内核日志机制,用户进程的标准输出通过`write`系统调用操作文件描述符,二者完全独立。 #### 2. “所有系统进程的输出都显示在物理控制台” - **错误**:系统服务(如`sshd`、`cron`)通常脱离终端运行,其输出被重定向到日志文件或丢弃。 --- ### 六、总结 - **内核启动的应用程序**(如`init`)的标准输入输出**默认可能指向控制台**,但实际行为受初始化系统和配置影响。 - **用户进程的标准流**通常由Shell或服务管理器动态分配,与控制台无必然关联。 - 若需确保进程输出控制台,需显式配置标准流重定向或调整内核启动参数。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值