前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解 | Linux系统是如何实现存储并读写文件的?

详解 | Linux系统是如何实现存储并读写文件的?

作者头像
开源519
发布2020-09-29 10:04:18
1.6K0
发布2020-09-29 10:04:18
举报
文章被收录于专栏:开源519开源519开源519

1. 概述

Linux系统文件操作主要是通过块设备驱动来实现的。 块设备主要指的是用来存储数据的设备,类似于SD卡、U盘、Nor Flash、Nand Flash、机械硬盘和固态硬盘等。块设备驱动就是用来访问这些存储设备的,其与字符设备驱动不同的是:

  • 块设备只能以块为基本单位实现读写,块是 linux 虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
  • 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中;字符设备是按照字节进行读写访问的。不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。

2. 代码框架

在记录块设备驱动的基本框架之前,先大致了解一下块设备驱动要实现的工作:在Linux驱动编程中,每一类驱动都会有一个对应的结构体。具体场景应用时,上层应用代码经过一系列虚拟文件系统API后最终会调用到驱动的这个结构体。应用所有对硬件的操作,都是通过调用此结构体的成员功能函数实现的。

对应设备驱动结构体定义于:include/linux/genhd.h

struct gendisk {
 /* major, first_minor and minors are input parameters only,
  * don't use directly.  Use disk_devt() and disk_max_parts().
  */
 int major;   /* major number of driver */
 int first_minor;
 int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

 char disk_name[DISK_NAME_LEN]; /* name of major driver */
 char *(*devnode)(struct gendisk *gd, umode_t *mode);

 unsigned int events;  /* supported events */
 unsigned int async_events; /* async events, subset of all */

 struct disk_part_tbl __rcu *part_tbl;
 struct hd_struct part0;

 const struct block_device_operations *fops;
 struct request_queue *queue;
 void *private_data;

 int flags;
 struct kobject *slave_dir;

 struct timer_rand_state *random;
 atomic_t sync_io;  /* RAID */
 struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
 struct kobject integrity_kobj;
#endif /* CONFIG_BLK_DEV_INTEGRITY */
 int node_id;
 struct badblocks *bb;
};

在设备驱动中,主要的工作就是在入口中实现对gendisk结构体成员的填充,并注册到系统中去,供上层调用。


在了解到块设备驱动需要做的大致工作后,就要在块设备驱动基础框架上实现这些工作。块设备驱动代码主要分为以下几个部分:

声明入口、出口函数

module_init();
module_exit();

入口函数

在入口函数中,实现的功能比较多: ① 申请数据缓存区

ramdisk.block_buf = kzalloc(RAMDISK_SIZE, GFP_KERNEL)

② 向文件系统注册块设备

ramdisk.major = register_blkdev(0, DEVICE_NAME);

③ 初始化请求队列

ramdisk.queue = blk_init_queue(ramdisk_request, &ramdisk.lock);

④ 申请gendisk结构体,实例成员,并注册到系统中

    ramdisk.gendisk = alloc_disk(3);
    ramdisk.gendisk->major = ramdisk.major;
    ramdisk.gendisk->first_minor = 0;
    ramdisk.gendisk->fops = &ramdisk_fops;
    ramdisk.gendisk->queue = ramdisk.queue;
    sprintf(ramdisk.gendisk->disk_name, "dx_ramdisk");
    set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);
    add_disk(ramdisk.gendisk);

出口函数

注销在入口函数中申请的结构体空间以及释放获取的动态内存。

3. 主要功能实现

内存操作

既然涉及到数据的读取与存储,必然需要实现对存储设备内存的操作。由于内存数据的读写都是以块为单位,故读写操作放在队列中实现。内存操作的接口ramdisk_request放在blk_init_queue初始化队列中,开发人员只需要实现ramdisk_request函数的功能即可。

这里简单地用内存来模拟磁盘,故用memcpy来实现数据读写功能。

static void ramdisk_transfer(struct request *req)
{ 
 unsigned long start = blk_rq_pos(req) << 9;   
 unsigned long len  = blk_rq_cur_bytes(req);  

 void *buffer = bio_data(req->bio);  
 
 if(rq_data_dir(req) == READ)   
  memcpy(buffer, ramdisk.block_buf + start, len);
 else if(rq_data_dir(req) == WRITE)  
  memcpy(ramdisk.block_buf + start, buffer, len);

}

void ramdisk_request(struct request_queue *q)
{
 int err = 0;
 struct request *req;

 req = blk_fetch_request(q);
 while(req != NULL) {
  ramdisk_transfer(req);
  if (!__blk_end_request_cur(req, err))
   req = blk_fetch_request(q);
 }
}

至于其他存储设备,就需要在ramdisk_request中实现对该存储设备的块读写操作。

4. 测试

① 注册驱动: insmod ramdisk.ko

② 查询磁盘状态:fdisk -l

③ 格式化磁盘:mkfs.vfat /dev/dx_ramdisk

④挂载磁盘:mount /dev/dx_ramdisk /dx_tmp1

由第④步即可看到,磁盘已经挂载到创建的dx_tmp1空文件夹上了。表明本次测试成功,系统就可以直接使用此磁盘来存储文件数据,

5. 总结

到这里,一个简单的块设备驱动就完成了。总结一下:在块设备驱动编程时,与字符设备驱动类似,需要实例操作系统提供的设备结构体成员,然后再将实例后的结构体注册到系统中,以供上层应用定向调用。需要注意的是,本篇实例是通过内存来模拟的块设备驱动,所以在实现存储区读写操作就比较简单。如果是针对具体的SPI FLASH、Nor FLASH、EEPROM等存储设备,还需要打通硬件读写功能。

参考:《【正点原子】I.MX6U嵌入式Linux驱动开发指南.pdf》

后记:

源码:https://github.com/LinuxTaoist/Linux_drivers/blob/master/block_driver/ramdisk.c

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源519 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. 代码框架
    • 声明入口、出口函数
      • 入口函数
        • 出口函数
        • 3. 主要功能实现
          • 内存操作
          • 4. 测试
          • 5. 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档