电子说
在这一节中,我们来一起学习和完成文件树中最后一个关键性内容——一切皆文件的设计理念。
所谓一切皆文件就是指计算机操作系统将一切计算机的可用资源都映射成文件形式向使用者提供统一的操作方式。我们在第一节时已经有了明确的讲述,在这一节中我们来看一下具体的设计理念和实现方法。我们在操作系统中为用户构建的虚拟系统中,树的每一个节点都是一个文件,而这些文件虽然有着不同的类型和功能,如普通文件、键盘、鼠标、打印机、显示器、内存页、操作系统调度状态等等,但却有着相同的文件操作接口。对于用户而言普通文件的操作通常只有“打开”、“关闭”、“读取”、“写入”这几个操作,而对于较为特殊的文件,通常还需要加入“输入输出控制”、“尝试获取数据”这两个操作,因此对于虚拟文件系统中的文件我们可以为其定义这样6个通用的操作:
typedef struct file_operations_s
{
int (*open)(void);
int (*close)(void);
size_t (*read)(void *, size_t);
size_t (*write)(const void *, size_t);
int (*ioctl)(unsigned int, unsigned long);
int (*poll)(unsigned int);
} file_operations_s;
typedef struct vfs_node_s
{
struct vfs_node_s *sibling;
struct vfs_node_s *child;
char name[NODE_NAME_SIZE];
struct file_operations_s ops;
} vfs_node_s;
在这里我们定义了一个结构体struct file_operations_s,这个结构体中定义了6个函数指针,用于表示这个文件的通用的6个操作。而具体的实现由注册这个设备节点的具体驱动程序来实现。接下来我们来实现当用户对某一个文件进行这6个操作时,虚拟文件系统的具体实现方法:
//打开文件
int open(char *path, int oflag, int mode)
{
vfs_node_s *node = fs_get_node(path);
pcb_s *pcb = sche_curr_pcb();
node- >ops.open();
uint32_t ind = fcntl_first_empty(pcb);
//申请节点
fcntl_alloc(pcb, ind);
pcb- >fnodes[ind] = node;
return ind;
}
//关闭文件
int close(int fd)
{
pcb_s *pcb = sche_curr_pcb();
vfs_node_s *node = pcb- >fnodes[fd];
int ret = node- >ops.close();
pcb- >fnodes[fd] = NULL;
fcntl_free(pcb, fd);
return ret;
}
//读取文件内容
size_t read(int fd, void *buf, size_t count)
{
pcb_s *pcb = sche_curr_pcb();
vfs_node_s *node = pcb- >fnodes[fd];
return node- >ops.read(NULL, buf, count);
}
//写入文件内容
size_t write(int fd, void *buf, size_t count)
{
pcb_s *pcb = sche_curr_pcb();
vfs_node_s *node = pcb- >fnodes[fd];
return node- >ops.write(NULL, buf, count);
}
//输入输出控制
int ioctl(int fd, unsigned int cmd, unsigned long arg)
{
pcb_s *pcb = sche_curr_pcb();
vfs_node_s *node = pcb- >fnodes[fd];
return node- >ops.ioctl(NULL, cmd, arg);
}
//尝试获取资源
int poll(int fd, unsigned int ms)
{
pcb_s *pcb = sche_curr_pcb();
vfs_node_s *node = pcb- >fnodes[fd];
return node- >ops.poll(ms);
}
最后,我们来具体看一下如何编写一个驱动程序,并将这个驱动程序注册为虚拟文件系统中的一个设备节点,即文件。用户又如何通过通用的操作来实现对此设备的控制。
最简单的,我们可以通过对一个GPIO引脚的拉高拉低来实现一个LED灯的亮和灭,使用Cortex-M3处理下的实现方式有两步,第一步:初始化GPIO引脚;第二步:对GPIO进行拉高或拉低,从而达到LED亮和灭的操作。具体实现程序如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
接下来,当我们需要点亮LED灯时,对PC4引脚拉低;当需要熄灭时,对PC4引脚拉高,于是程序如下:
//点亮,拉低
GPIO_WriteBit(GPIOC, GPIO_Pin_4, 0);
//熄灭,拉高
GPIO_WriteBit(GPIOC, GPIO_Pin_4, 1);
对于驱动程序而言,我们不希望用户在使用LED时编写较为复杂的、直接操作硬件的代码,而是希望操作系统为用户提供一个通用的操作接口函数,于是我们就可以编写一个驱动程序,并向操作系统注册一个"/dev/led"设备节点,而"/dev/led"这个文件就是一个设备文件,它的内部由驱动开发人员完成与硬件交互的功能,对使用人员则只提供open()、close()、read()、write()、ioctl()、poll()等操作函数。对于LED灯来说,驱动程序比较简单我们只实现其open()、close()、ioctl()这3个函数。有兴趣的读者可以自行实现read()、write()、poll()等函数:
#define LED_IOCTL_ON (0)
#define LED_IOCTL_OFF (0)
void led_drv_init(void)
{
file_operations_s ops = {0};
ops.open = led_open;
ops.close = led_close;
ops.read = NULL;
ops.write = NULL;
ops.ioctl = led_ioctl;
ops.poll = NULL;
fs_register_dev("/dev/led", ops);
}
int led_open(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
return 0;
}
int led_close(void)
{
return 0;
}
int led_ioctl(void, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case LED_IOCTL_ON:
GPIO_WriteBit(GPIOC, GPIO_Pin_4, 0);
break;
case LED_IOCTL_OFF:
GPIO_WriteBit(GPIOC, GPIO_Pin_4, 1);
break;
default:
break;
}
return 0;
}
这样我们就编写了一个LED灯的驱动程序,并在操作系统中注册为"/dev/led"设备节点,即文件。用户可以通过以下方式来操作这个LED设备:
int fd = open("/dev/led");
//点亮
ioctl(fd, LED_IOCTL_ON);
//熄灭
ioctl(fd, LED_IOCTL_OFF);
close(fd);
对于用户而言,操作这个硬件LED灯就与操作普通文件一样,通过open()函数打开这个文件,通过ioctl这个函数对这个文件进行相关的控制,使用完毕之后再通过close()函数关闭此文件,于是,硬件与用户之间就减少了很多特定的功能操作,用户也不必关心硬件设备的具体实现细节,只需要对这个设备文件进行通用操作即可。而对于编写驱动程序的人员来说,只需要将硬件相关的程序和操作封装到驱动程序内部即可,无需暴露给用户。这样就实现了“一切皆文件”的设计理念。
全部0条评论
快来发表一下你的评论吧 !