源起
rtems自带了一些示例程序,在testsuites/samples/hello/init.c 中,是一个非常简单的示例,相当于C里面的hello world了,要实现的功能只是打印出来一句问候
rtems_task Init(
rtems_task_argument ignored
)
{
rtems_test_begin();
printf( "Hello World\n" );
rtems_test_end();
exit( 0 );
}
对于PC来说,这句一般来说是显示到屏幕上面了,那么对于一块嵌入式板子呢?
问候的话去哪了
对于C程序来说,有三个标准的输入输出,分别是stdin stdout stderr, 对于printf来说,它的输出应该是去stdout了
newlib/libc/stdio/printf.c
int _DEFUN(printf, (fmt), const char *__restrict fmt _DOTS)
{
int ret;
va_list ap;
struct _reent *ptr = _REENT;
那么stdout是如何定义呢?
stdout的定义
rtems使用的是newlib这个C库,在这里可以找到如下定义
newlib/libc/include/stdio.h
#define stdout (_REENT->_stdout)
那么_REENT的定义呢?
newlib/libc/include/sys/reent.h
#define _REENT (__getreent())
__getreent的定义
cpukit/sapi/include/confdefs.h
#if defined(RTEMS_NEWLIB) && defined(__DYNAMIC_REENT__)
struct _reent *__getreent(void)
{
#ifdef CONFIGURE_DISABLE_NEWLIB_REENTRANCY
return _GLOBAL_REENT;
#else
return _Thread_Get_executing()->libc_reent;
#endif
}
#endif
嗯,终于找到了,原来是在这里
cpukit/score/include/rtems/score/thread.h
struct Thread_Control_struct{
...
/** This field points to the newlib reentrancy structure for this thread. */
struct _reent *libc_reent;
...
}
stdout的初始化
printf --> _vfprintf_r --> CHECK_INIT --> __sinit
newlib/libc/stdio/findfp.c
_VOID _DEFUN(__sinit, (s), struct _reent *s)
{
...
s->_stdin = __sfp(s);
s->_stdout = __sfp(s);
s->_stderr = __sfp(s);
...
std (s->_stdin, __SRD, 0, s);
std (s->_stdout, __SWR, 1, s);
std (s->_stderr, __SRW | __SNBF, 2, s);
s->__sdidinit = 1;
...
}
到这里可以看到,在使用输出的时候,newlib把_stdout -> _file 设置为 1, 把 _stderr->_file设置为2, 把_stdin->_file设置为0, 这里的数值,其实就是文件号fd,文件系统会根据文件号fd,找到设备的(major, minor),从而最终输出。
谁是fd 1
rtems里面的fd,是 rtems_libio_iops 数组的下标,从write的例子中可以看到
cpukit/libcsupport/src/write.c
ssize_t write( int fd, const void *buffer, size_t count )
{
rtems_libio_t *iop;
iop = rtems_libio_iop( fd );
...
return (*iop->pathinfo.handlers->write_h)( iop, buffer, count );
}
boot_card --> bsp_postdriver_hook --> rtems_libio_post_driver
void rtems_libio_post_driver(void)
{
int stdin_fd;
int stdout_fd;
int stderr_fd;
/*
* Attempt to open /dev/console.
*/
if ((stdin_fd = open("/dev/console", O_RDONLY, 0)) == -1) {
/*
* There may not be a console driver so this is OK.
*/
return;
}
/*
* But if we find /dev/console once, we better find it twice more
* or something is REALLY wrong.
*/
if ((stdout_fd = open("/dev/console", O_WRONLY, 0)) == -1)
rtems_fatal_error_occurred( 0x55544431 ); /* error STD1 */
if ((stderr_fd = open("/dev/console", O_WRONLY, 0)) == -1)
rtems_fatal_error_occurred( 0x55544432 ); /* error STD2 */
}
这是rtems首次打开文件,从上到下,fd分别是0, 1, 2, 对应stdin stdout stderr, 也就是说一个设备文件打开了三次,对应着程序的标准输入输出
对于/dev/console, 一般是应用程序来指定哪个串口充当console,如果没有指定,那么rtems会选择第一个可用的串口设备充当console(详细过程不是本文重点,不详述)
每个task的标准输入输出都是一样的么?
默认来说,每个task的标准输出输入都是一样的,因为对应的fd都是初始化为0, 1, 2 。 但是通过修改stdin stdout stderr,可以使得每个任务的标准输入输出不同,例子可以看cpukit/telnetd/telnetd.c文件中的spawned_shell函数。
这里要说明的是,每个任务对stdio的支持
cpukit/sapi/include/confdefs.h
typedef struct {
Thread_Control Control;
...
#if !defined(RTEMS_SCHEDSIM) \
&& defined(RTEMS_NEWLIB) \
&& !defined(CONFIGURE_DISABLE_NEWLIB_REENTRANCY)
struct _reent Newlib;
#else
struct { /* Empty */ } Newlib;
#endif
...
}
const Thread_Control_add_on _Thread_Control_add_ons[] = { { ...
{ offsetof(Configuration_Thread_control, Control.libc_reent ), offsetof( Configuration_Thread_control, Newlib ) } ... }
每个任务都有自己的stdio指针,所以可以做到每个任务的标准输入输出不同。说到这里,rtems的maillist中有对这个讨论:
http://lists.rtems.org/pipermail/users/2011-March/023301.html
rtems可以看做一个大的进程,里面每一个任务都是一个线程,而FILE*列表对于一个进程里面的线程来说是全局的(假如一个线程打开了一个文件,然后这个线程退出,那么被打开的文件还会继续维持打开状态),所以有人认为newlib使得每个线程都可以有不同stdio,是挺奇怪的做法。摘抄一下
HOWEVER: stdin/stdout/stderr are treated differently by newlib.These three FILEs are 'per-thread' entities and are destroyed(albeit this currently may still leak memory, see PR #1246)when a thread is deleted.Note that the stdin/stdout/stderr FILEs are 'lazy-initialized'upon first use, i.e., a thread which doesn't use them doesn'tcreate them.
-> stdin/stdout/stderr refer to different streams/buffers for each thread (but read/write the same underlying file descriptors) and the buffers are 'attached' to the thread's 'reent' struct.
-> other FILEs are global and attached to the global 'reent' struct.