本文说明Zephyr的fdtable和posix标准输出的实现。
和Linux相似,Zephyr也有自己的fdtable用来管理文件描述符,相对的Zephyr的fdtable实现很简单。所有可以抽象为read/write/close/ioctl的模块都可以注册到fdtable中,通过对fd的read/write/close/ioctl统一形式API来操作,目前Zephyr的fdtable主要用在posix文件系统和socket的fd管理。本文主要分析fdtable的实现以及起在posix下标准输入输出上的使用。
API说明
include/sys/fdtable.h中定义了对外API和数据结构
数据结构
文件描述符的虚方法表,提供了标准抽象:读,写,关闭,其它操作都是通过ioctl来完成1
2
3
4
5
6struct fd_op_vtable {
ssize_t (*read)(void *obj, void *buf, size_t sz);
ssize_t (*write)(void *obj, const void *buf, size_t sz);
int (*close)(void *obj);
int (*ioctl)(void *obj, unsigned int request, va_list args);
};
API
int z_reserve_fd(void)
预定文件描述符,返回值就是int型的描述符
void z_finalize_fd(int fd, void *obj, const struct fd_op_vtable *vtable)
作用创建文件描述符,在z_reserve_fd后只调用一次,将I/O的对象obj和操作方法表vtable注册给文件描述符
int z_alloc_fd(void *obj, const struct fd_op_vtable *vtable)
分配文件描述符,其作用就是z_reserve_fd和z_finalize_fd的组合
void z_free_fd(int fd)
释放描述符
void *z_get_fd_obj(int fd, const struct fd_op_vtable *vtable, int err)
获取文件描述符操作的IO对象,返回的就是z_finalize_fd注册的obj,如果vtable不为NULL,将匹配vtable和注册时一致才会返回obj
void *z_get_fd_obj_and_vtable(int fd, const struct fd_op_vtable **vtable)
获取文件描述符操作的IO兑现,返回的是z_finalize_fd注册的obj,输出的vtable是z_finalize_fd注册的vtable
static inline int z_fdtable_call_ioctl(const struct fd_op_vtable *vtable, void *obj,unsigned long request, ...)
调用vtable中的ioctrl,request和posix函数fcntl的cmd参数定义一致,zephyr定义了0x100以上的几个私有的request,如下:1
2
3
4
5
6
7
8enum {
/* Codes below 0x100 are reserved for fcntl() codes. */
ZFD_IOCTL_FSYNC = 0x100,
ZFD_IOCTL_LSEEK,
ZFD_IOCTL_POLL_PREPARE,
ZFD_IOCTL_POLL_UPDATE,
ZFD_IOCTL_POLL_OFFLOAD,
};
实现
fdtable实现代码在lib/os/fdtable.c中,实现方法比较简单。在其内部维护一个struct fd_entry
的数组fdtable1
2
3
4
5struct fd_entry {
void *obj; //IO object
const struct fd_op_vtable *vtable; //文件描述符操作方法表
atomic_t refcount; //文件描述符引用次数
};
fdtable的大小由CONFIG_POSIX_MAX_FDS
配置
在预定fd时会顺序检查fdtable,取出一个refcount为0的成员,将其index序号做为fd返回,代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35static int _find_fd_entry(void)
{
int fd;
//顺序检查
for (fd = 0; fd < ARRAY_SIZE(fdtable); fd++) {
//找出refcount为0的成员,返回其在fdtable的index作为fd
if (!atomic_get(&fdtable[fd].refcount)) {
return fd;
}
}
errno = ENFILE;
return -1;
}
int z_reserve_fd(void)
{
int fd;
(void)k_mutex_lock(&fdtable_lock, K_FOREVER);
//找到没有使用的fd
fd = _find_fd_entry();
if (fd >= 0) {
//增加refcount引用,表示该fd已用
(void)z_fd_ref(fd);
//obj和vtable将在z_finalize_fd中设置
fdtable[fd].obj = NULL;
fdtable[fd].vtable = NULL;
}
k_mutex_unlock(&fdtable_lock);
return fd;
}
z_finalize_fd实现很简单,就是将obj和vtable设置到fdtable内1
2
3
4
5void z_finalize_fd(int fd, void *obj, const struct fd_op_vtable *vtable)
{
fdtable[fd].obj = obj;
fdtable[fd].vtable = vtable;
}
z_alloc_fd就是z_reserve_fd和z_finalize_fd的组合,这里不再分析代码
z_get_fd_obj和z_get_fd_obj_and_vtable也是通过fd查表fdtable,取得obj和vtable,代码简单这里不做分析。
z_free_fd是释放掉占用的fdtable1
2
3
4
5void z_free_fd(int fd)
{
//对refcount进行减一操作
(void)z_fd_unref(fd);
}
可以从下面的code看到z_fd_ref是对refcount加一,z_fd_unref对refcount减一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27static int z_fd_ref(int fd)
{
return atomic_inc(&fdtable[fd].refcount) + 1;
}
static int z_fd_unref(int fd)
{
atomic_val_t old_rc;
//refcount减一操作
do {
old_rc = atomic_get(&fdtable[fd].refcount);
if (!old_rc) {
return 0;
}
} while (!atomic_cas(&fdtable[fd].refcount, old_rc, old_rc - 1));
//如果还有其它引用就返回
if (old_rc != 1) {
return old_rc - 1;
}
//没有引用就清空obj和vtable
fdtable[fd].obj = NULL;
fdtable[fd].vtable = NULL;
return 0;
}
虽然在实现fdtable的管理里面使用了ref机制,但目前fdtable ref只会在z_reserve_fd被增加一次,因此z_free_fd调用z_fd_unref就会被释放。
POSIX API
当zephyr配置了CONFIG_POSIX_API=y
时,会使用fdtable中实现的标准IO函数,并实现标准输入输出。
标准IO
标准IO上实现了,read/write/close/fsync/lseek/ioctl/fcntl, libc的桩函数也会直接链接到这些里面来
这些标准IO实现上就是直接调用对应fd 的vtable内函数,例如read,就是调用fd对应fdtable[fd].vtable->read,其它的就不再列出可以到fdtable中查看1
2
3
4
5
6
7
8ssize_t read(int fd, void *buf, size_t sz)
{
if (_check_fd(fd) < 0) {
return -1;
}
return fdtable[fd].vtable->read(fdtable[fd].obj, buf, sz);
}
标准输入输出
在配置CONFIG_POSIX_API=y
后, fdtable默认注册了标准输入输出的fd1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22static struct fd_entry fdtable[CONFIG_POSIX_MAX_FDS] = {
#ifdef CONFIG_POSIX_API
/*
* Predefine entries for stdin/stdout/stderr.
*/
{
/* STDIN */
.vtable = &stdinout_fd_op_vtable,
.refcount = ATOMIC_INIT(1)
},
{
/* STDOUT */
.vtable = &stdinout_fd_op_vtable,
.refcount = ATOMIC_INIT(1)
},
{
/* STDERR */
.vtable = &stdinout_fd_op_vtable,
.refcount = ATOMIC_INIT(1)
},
#endif
};
可以看到fd=0是标准输入,fd=1是标准输出,fd=2是标准error,三者都使用了stdinout_fd_op_vtable1
2
3
4
5static const struct fd_op_vtable stdinout_fd_op_vtable = {
.read = stdinout_read_vmeth,
.write = stdinout_write_vmeth,
.ioctl = stdinout_ioctl_vmeth,
};
从标准输入中读将调用到stdinout_read_vmeth, 向标准输出写会调用到stdinout_write_vmeth, 对标准输入输出进行控制会调用到stdinout_ioctl_vmeth,在目前的实现中没有支持标准输入和控制,因此代码中stdinout_read_vmeth/stdinout_ioctl_vmeth是留的空函数,这里分析标准输出函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count)
{
return z_impl_zephyr_write_stdout(buffer, count);
#endif
}
int z_impl_zephyr_write_stdout(const void *buffer, int nbytes)
{
const char *buf = buffer;
int i;
for (i = 0; i < nbytes; i++) {
if (*(buf + i) == '\n') {
_stdout_hook('\r');
}
_stdout_hook(*(buf + i));
}
return nbytes;
}
标准输出也就是对fd为1进行写,就会调用到z_impl_zephyr_write_stdout,该操作会自动将\n变为\r\b,然后调用_stdout_hook进行输出。
_stdout_hook由__stdout_hook_install进行安装,一般情况下是安装到串口,可以用串口中断产看1
Uart_console.c (drivers\console): __stdout_hook_install(console_out);
也可以根据需要安装到其它地方,例如下面两种是向调试器写,可以在host上用工具查看1
2Semihost_console.c (drivers\console): __stdout_hook_install(semihost_console_out);
Rtt_console.c (drivers\console): __stdout_hook_install(rtt_console_out);
下面这一种是写到ram中,也是为了调试用1
Ram_console.c (drivers\console): __stdout_hook_install(ram_console_out);
下面这种是写到蓝牙monitor1
Monitor.c (subsys\bluetooth\host): __stdout_hook_install(monitor_console_out);
文件系统
fdtable中提供了标准的read/write/close/ioctl,哪open哪里去了呢,其实open就是从fdtable中获取描述符,然后将自己的read/write/close/ioctl注册进去,lib/posix/fs.c中open的流程可以看到这一点,下面是简化后的code1
2
3
4
5
6
7
8
9
10
11
12
13int open(const char *name, int flags, ...)
{
fd = z_reserve_fd();
ptr = posix_fs_alloc_obj(false);
rc = fs_open(&ptr->file, name, zmode);
z_finalize_fd(fd, ptr, &fs_fd_op_vtable);
return fd;
}
可以看到从fdtable申请到fd,将正在文件系统的操作函数注册到vtable内1
2
3
4
5
6static struct fd_op_vtable fs_fd_op_vtable = {
.read = fs_read_vmeth,
.write = fs_write_vmeth,
.close = fs_close_vmeth,
.ioctl = fs_ioctl_vmeth,
};
实际执行read的时候就是执行的fs_read_vmeth, 最终还是操作的fs_read函数。 write/close/ioctl类似,不再做展开分析1
2
3
4
5
6
7
8
9
10
11
12
13static ssize_t fs_read_vmeth(void *obj, void *buffer, size_t count)
{
ssize_t rc;
struct posix_fs_desc *ptr = obj;
rc = fs_read(&ptr->file, buffer, count);
if (rc < 0) {
errno = -rc;
return -1;
}
return rc;
}
关于socket
socket和文件系统使用fdtable的方式是一致的,只是socket除了标准的read/write/ioctl还有其它API,在之后的offload socket会详细分析,这里就不展开了。