linux5.16 armv8-64

busybox1.30 sh=ash

console概念

console即控制台是用来输入输出系统信息的设备。包括keyboard,mouse字符类型的设备,也包括显示器图形类型的设备。控制台是一个特殊的终端,区别于一般的终端,系统的错误信息,开机信息只会打印在控制台这个特殊终端上。

Linux Terminal and Console Explained For Beginners - LinuxBabe

linux console 结构体定义如下

struct console {
	char	name[16];
	void	(*write)(struct console *, const char *, unsigned);
	int	(*read)(struct console *, char *, unsigned);
	struct tty_driver *(*device)(struct console *, int *);
	void	(*unblank)(void);
	int	(*setup)(struct console *, char *);
	int	(*exit)(struct console *);
	int	(*match)(struct console *, char *name, int idx, char *options);
	short	flags;
	short	index;
	int	cflag;
	uint	ispeed;
	uint	ospeed;
	void	*data;
	struct	 console *next;
};

console初始化

bootcmd  console=ttyAMA0 以tty console为例

start_kernel{
        console_init --> console_initcall
        parse_early_param --> console_setup
}

console_init是遍历所有console_initcall注册console

console_setup是设置console=ttyAMA0 作为preferred_console,The last preferred console added will be used for kernel messages and stdin/out/err for init.

/*
 * Initialize the console device. This is called *early*, so
 * we can't necessarily depend on lots of kernel help here.
 * Just do some early initializations, and do the complex setup
 * later.
 */
void __init console_init(void)
{
	int ret;
	initcall_t call;
	initcall_entry_t *ce;

	/* Setup the default TTY line discipline. */
	n_tty_init();

	/*
	 * set up the console device so that later boot sequences can
	 * inform about problems etc..
	 */
	ce = __con_initcall_start;
	trace_initcall_level("console");
	while (ce < __con_initcall_end) {
		call = initcall_from_entry(ce);
		trace_initcall_start(call);
		ret = call();
		trace_initcall_finish(call, ret);
		ce++;
	}
}

serial console

static struct uart_driver amba_reg;
static struct console amba_console = {
	.name		= "ttyAMA",
	.write		= pl011_console_write,
	.device		= uart_console_device,
	.setup		= pl011_console_setup,
	.match		= pl011_console_match,
	.flags		= CON_PRINTBUFFER | CON_ANYTIME,
	.index		= -1,
	.data		= &amba_reg,
};

uart_add_one_port --> uart_config_port --> register_console(port->cons)注册console --> try_enable_new_console-->console.match

register console name与console cmdline中的console=后面的字符串ttyAMA0做匹配,匹配成功调用console.setup

console输入过程

查看console的输入过程前先要了解如下概念       

以本例console=ttyAMA0,系统启动后init程序会根据inittab中getty配置来启动getty负责读取console的输入,然后getty会先调用login程序获取console输入作为登录信息,登录成功之后有sh程序获取console的输入。

n_tty是serial port默认的线路规程

线路规程 [Line disciplines provide an elegant mechanism that lets you use the same serial driver to run different technologies. The low-level physical driver and the tty driver handle the transfer of data to and from the hardware, while line disciplines are responsible for processing the data and transferring it between kernel space and user space.]

tty_port  client  uart_port

程序调用路径如下

vfs_read  --> n_tty_read --> wait_woken,将sh读线程休眠在n_tty->read_wait的等待队列上

输入字符后串口先获取到字符

irq --> p1011_dma_rx_riq --> pl011_dma_rx_chars --> tty_flip_buffer_push --> 提交工作队列

void tty_flip_buffer_push(struct tty_port *port)
{
	struct tty_bufhead *buf = &port->buf;

	tty_flip_buffer_commit(buf->tail);
	queue_work(system_unbound_wq, &buf->work);
}

void tty_buffer_init(struct tty_port *port)
{
	struct tty_bufhead *buf = &port->buf;

	mutex_init(&buf->lock);
	tty_buffer_reset(&buf->sentinel, 0);
	buf->head = &buf->sentinel;
	buf->tail = &buf->sentinel;
	init_llist_head(&buf->free);
	atomic_set(&buf->mem_used, 0);
	atomic_set(&buf->priority, 0);
	INIT_WORK(&buf->work, flush_to_ldisc);
	buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;
}

flush_to_ldisc --> receive_buf --> tty_port->client_ops->receive_buf --> tty_ldisc_receive_buf

--> ld->ops->receive_buf(n_tty_receive_buf) --> n_tty_receive_buf_common --> __receive_buf

--> wake_up_interruptible_poll 唤醒 tty->read_wait 等待队列

const struct tty_port_client_operations tty_port_default_client_ops = { 
    .receive_buf = tty_port_default_receive_buf,
    .write_wakeup = tty_port_default_wakeup,
};
static int tty_port_default_receive_buf(struct tty_port *port,
                    const unsigned char *p, 
                    const unsigned char *f, size_t count)
{
    int ret;
    struct tty_struct *tty;
    struct tty_ldisc *disc;

    tty = READ_ONCE(port->itty);
    if (!tty)
        return 0;

    disc = tty_ldisc_ref(tty);
    if (!disc)
        return 0;

    ret = tty_ldisc_receive_buf(disc, p, (char *)f, count);

    tty_ldisc_deref(disc);

    return ret;
}

console 回显过程

关闭与打开回显的命令stty -echo/echo

stty -echo 关闭回显后敲下回车依然会显示终端提示符#,提示符#由环境变量$PS1决定,PS1一般是sh第一次设置的,当然环境变量在系统启动后的随时可以修改(终端执行echo $0 结果是ash,top命令中-sh线程也是ash,-代表是interactive shell,也是login shell,login shell会读配置文件接入终端的输入输出,用户密码登录后的sh是login shell)。测试机器使用的是busybox的ash作sh,init启动sh后sh处理终端的输入与输出,提示符#不属于回显内容,是sh处理完一条命令主动写到stdout的

stty 代码实现为ioctl termios,echo属于termios local功能

DESCRIPTION
       The  termios  functions  describe  a general terminal interface that is provided to control asynchronous
       communications ports.

   The termios structure
       Many of the functions described here have a termios_p argument that is a pointer to a termios structure.
       This structure contains at least the following members:

           tcflag_t c_iflag;      /* input modes */
           tcflag_t c_oflag;      /* output modes */
           tcflag_t c_cflag;      /* control modes */
           tcflag_t c_lflag;      /* local modes */
           cc_t     c_cc[NCCS];   /* special characters */

tty_ioctl --> n_tty_ioctl --> n_tty_ioctl_helper --> tty_mode_ioctl --> set_termios --> tty_set_termios

process_echo

*	process_echoes	-	write pending echo characters
 *	@tty: terminal device
 *
 *	Write previously buffered echo (and other ldisc-generated)
 *	characters to the tty.
 *
 *	Characters generated by the ldisc (including echoes) need to
 *	be buffered because the driver's write buffer can fill during
 *	heavy program output.  Echoing straight to the driver will
 *	often fail under these conditions, causing lost characters and
 *	resulting mismatches of ldisc state information.

回显功能以前了解是线路规程n_tty处理的,如果没有pending的回显字符,回显会直接写到驱动中,用gdb查看回显的字符竟然是是sys_write在写,怎么感觉像跟sh有关

vfs_write --> call_write_iter --> new_sync_write --> tty_write --> file_tty_write  --> do_tty_write --> n_tty_write --> process_output_block --> uart_write

debug sh程序看确实是sh写到的标准输出中的

strace -p pid_of_shell
终端输入5

ppoll([{fd=0, events=POLLIN}], 1, NULL, NULL, 0) = 1 ([{fd=0, revents=POLLIN}])
read(0, "5", 1)                         = 1
write(1, "5", 15)                        = 1

console输出过程

printk --> _printk --> vprintk --> vprintk_default --> vprintk_emit --> console_unlock --> pl011_console_write --> pl011_console_putchar

printk中的打印信息在vprintk_emit 函数中构造成struct printk_record +然后存入到printk _ringbuf log_buf中

struct printk_record {
	struct printk_info	*info;
	char			*text_buf;
	unsigned int		text_buf_size;
};

struct printk_info {
	u64	seq;		/* sequence number */
	u64	ts_nsec;	/* timestamp in nanoseconds */
	u16	text_len;	/* length of text message */
	u8	facility;	/* syslog facility */
	u8	flags:5;	/* internal record flags */
	u8	level:3;	/* syslog level */
	u32	caller_id;	/* thread id or processor id */

	struct dev_printk_info	dev_info;
};

console_unlock调用call_console_drivers foreach所有的console输出log_buf里的内容

* While the console_lock was held, console output may have been buffered
 * by printk().  If this is the case, console_unlock(); emits
 * the output prior to releasing the lock.
 *
 * If there is output waiting, we wake /dev/kmsg and syslog() users.