1.1 console
console 和 tty 有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问。console可以是串口,可以是vga,也可以是LCD等设备。
1.2 控制台初始化
在系统启动的流程中会走入板级初始化的流程中,板级初始化中包含了所有外设的初始化,但是不包含串口 uart,uart
的初始化需要放在班级初始化之前,这样在板级设备初始化的过程中才能看到相关的串口 log 打印,方便调试:
void rt_hw_board_init()
console_uart_init(); //UART 初始化
rt_console_set_device(RT_CONSOLE_DEVICE_NAME); //console 初始化
rt_components_board_init();
从上面可以看到console的初始化是在板子初始化的流程中的,在 rt-thread
中对于 console 的设置是通过下面函数完成的:
rt_device_t rt_console_set_device(const char *name)
需要注意该函数传入的参数 #define RT_CONSOLE_DEVICE_NAME "uart0"
,在 rt_console_set_device
中会根据传入的参数名来找对应的 device
:
new_device = rt_device_find(name);
我们看下该函数 rt_console_set_device
的具体实现:
/**
* This function will set a device as console device.
* After set a device to console, all output of rt_kprintf will be
* redirected to this new device.
*
* @param name the name of new console device
*
* @return the old console device handler on successful, or RT_NULL on failure.
*/
rt_device_t rt_console_set_device(const char *name)
{
rt_device_t new_device, old_device;
/* save old device */
old_device = _console_device;
/* find new console device */
new_device = rt_device_find(name); //根據傳入的參數找到將要配置console device 的 device。
/* check whether it's a same device */
if (new_device == old_device) return RT_NULL;
if (new_device != RT_NULL) {
if (_console_device != RT_NULL) {
/* close old console device */
rt_device_close(_console_device);
}
/* set new console device */
rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
_console_device = new_device;
}
return old_device;
}
该函数的作用是 使用一个新设备用来当做 console 设备,新设备设置好之后,所有的 rt_kprintf
的输出都会重定向到这个新device中, 一般 rt_kprintf 的输出都是通过先 open 一个 device 然后再 write 数据到这个 device 的流程。接下来看下 rt_kprintf 的实现:
rt-thread/rt-thread/src/kservice.c
/**
* This function will print a formatted string on system console
*
* @param fmt the format
*/
RT_WEAK void rt_kprintf(const char *fmt, ...)
{
va_list args;
rt_size_t length;
static char rt_log_buf[RT_CONSOLEBUF_SIZE];
va_start(args, fmt);
/* the return value of vsnprintf is the number of bytes that would be
* written to buffer had if the size of the buffer been sufficiently
* large excluding the terminating null byte. If the output string
* would be larger than the rt_log_buf, we have to adjust the output
* length. */
length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
if (length > RT_CONSOLEBUF_SIZE - 1)
length = RT_CONSOLEBUF_SIZE - 1;
if (_console_device == RT_NULL) {
rt_hw_console_output(rt_log_buf);
} else {
rt_device_write(_console_device, 0, rt_log_buf, length); //走这里
}
va_end(args);
}
RTM_EXPORT(rt_kprintf);
由于 _console_device 不为空所以走的是 rt_device_write函数。下面看下rt_device_write
的实现:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
RT_ASSERT(dev != RT_NULL);
RT_ASSERT(rt_object_get_type(&dev->parent) == RT_Object_Class_Device);
if (dev->ref_count == 0) {
rt_set_errno(-RT_ERROR);
return 0;
}
/* call device_write interface */
if (device_write != RT_NULL) {
return device_write(dev, pos, buffer, size);
}
/* set error code */
rt_set_errno(-RT_ENOSYS);
return 0;
}
RTM_EXPORT(rt_device_write);
从上面 return device_write(dev, pos, buffer, size) 看到,通过调用 rt-thread 的标准接口 “device_write
“ 来进行写操作,所以我们就需要知道device
(_console_device
)的 ops函数是在哪里实现的了?
1.2.1 uart 设备注册
在 uart 初始化的过程中,会对 uart 进行设备注册,
int dw_uart_hw_init(struct uart_config *config)
rt_hw_serial_register(ser, config->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_R
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | flag);
后续通过已经注册过的 uart
设备的 name 可以拿到device句柄,此外还需要关注注册是填入的flag,当时在调试 ulog 异步输出模式的时候由于flag的问题导致 console 没能够接受输入。
1.3 shell 初始化
系统启动后,会进行shell的初始化:
INIT_APP_EXPORT(finsh_system_init)
shell 会创建一个线程,等待输入,目前在rtthread中使用的POSXI接口,finsh_getchar中调用getchar来获取字符的输入,getchar使用的arm libc,所以会通过先open 设备然后再读设备的流程:
const static struct dfs_file_ops _serial_fops =
{
serial_fops_open,
serial_fops_close,
serial_fops_ioctl,
serial_fops_read,
serial_fops_write,
RT_NULL, /* flush /RT_NULL, / lseek /RT_NULL, / getdents */
serial_fops_poll,
};
其中_serial_fops是再注册uart设备的时候传入的:
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
device->init = rt_serial_init;
device->open = rt_serial_open;
device->close = rt_serial_close;
device->read = rt_serial_read;
device->write = rt_serial_write;
device->control = rt_serial_control;
#endif
device->user_data = data;
/* register a character device */
ret = rt_device_register(device, name, flag);
#if defined(RT_USING_POSIX)
/* set fops */
device->fops = &_serial_fops;
#endif
return ret;
}