Xen I/O虚拟化原理——Xen下总线、设备和驱动

来源:互联网 发布:c4d r16 mac 注册机 编辑:程序博客网 时间:2024/06/06 03:53

最近对Xen下虚拟机的Xenbus、驱动和设备的创建和添加机制很模糊,看了这篇博客之后思路清晰了很多,特别转载下来留作记录,感谢作者。另原文的内容有少许更改。

博客原文链接【http://blog.csdn.net/easyblue99/article/details/8238005】

虚拟化总线、驱动和设备

4 io设备虚拟化

Io设备的虚拟化,不可避免要涉及到设备的概念和隐藏在设备背后的总线。理解了这两个概念,就比较容易理解io设备的虚拟化。

4.1   设备,总线和驱动

设备是计算机系统中一个重要概念。通常的显卡网卡声卡等设备,都是先插入计算机系统的PCI总线插槽(早期还有ISA,MCA总线等。现在PC领域基本PCI总线统一了),然后安装驱动,之后应用程序可以通过文件系统打开和读写设备文件。这个过程可以从三个层面理解:设备本身的特性,总线和操作系统对设备的处理,驱动层次。

PCI设备为例,一个PCI设备,本身就包含一个配置表。配置表包含设备制造商填充的厂商信息,设备属性等等的通用配置信息。此外,设备厂商还应该提供设备的控制寄存器信息,通过这些控制寄存器,系统可以设置设备的状态,控制设备的运行,或者从设备获得状态信息。另外设备还可能配备了内存(也有的设备可能没有),系统可以读写设备的内存。

设备本身有一些配置信息。配置信息里面的设备内存基址,指示了设备内存的地址和长度,而设备寄存器基址,则指向了设备的寄存器地址和长度。该设备有两个寄存器,一个输入寄存器,一个输出寄存器。当输入寄存器写入数值后,可以从输出寄存器读到另一个数值。

设备寄存器基址,这个概念有点难理解。实际上,可以看做是一个地址,对这个地址写指令,就可以控制设备。所以,设备寄存器其实就是设备的控制接口。这个接口必须要映射到计算机系统的io空间,这样内核就可以访问设备了。

4.1.1 io端口和io内存

不同的处理器对io访问有不同的处理方式。对X86系统来说,专门提供了特别的指令来访问设备寄存器。所有这些设备寄存器占据了65536个8位的空间。这个空间称为计算机的IO端口空间。

对上文的例子设备来说,需要把设备的寄存器基址纳入到系统的IO端口空间里面,然后驱动就可以通过系统提供的特别指令来访问设备的寄存器。假设设备厂商提供的寄存器基址是0x1c00,长度是8个字节。那么有两种情况,一种是这个0x1c00地址和别的设备没有冲突,可以直接使用,那么操作系统内核就记录设备的寄存器基址为0x1c00,驱动通过X86系统提供的io指令访问0x1c00 io地址,或者叫0x1c00 io端口,就可以设置设备输入寄存器的内容。访问地址0x1c04,就可以读到设备输出寄存器的内容。

另外一种情况是其它设备也使用了0x1c00这个io地址。那么操作系统内核就需要寻找一个合适的寄存器基址,然后更新设备的寄存器基址,并记录到内核的设备信息里面。驱动使用x86的io指令,访问这个更新的地址,就可以设置设备输入寄存器的内容了。

通过设备的io端口访问设备寄存器来控制设备,这就是设备驱动的功能。设备厂商会提供设备寄存器的详细内容,这也是驱动开发者所必须关注的。而发现设备,扫描设备信息,为设备提供合适的io地址空间,这是内核的总线部分要处理的事情。后文将继续分析。

设备的内存处理过程差不多一样。内核同样要读取设备内存基址,然后找到合适的内存空间,把设备的内存映射到内存空间。然后驱动就可以标准的内存接口访问设备的内存了。

4.1.2 总线

设备的配置信息提供了设备寄存器基址和设备内存基址。因此首先要读到这两个寄存器的内容,也就是设备寄存器基址和长度以及设备内存基址和长度,然后操作系统才能安排合适的io端口和io内存。

但是如何去读设备的配置信息?X86系统使用的PCI总线对这个问题的解决方法是:保留了8个字节的io端口地址,就是0xCF8~0xCFF。要访问设备的某个配置信息,先往0xCF8地址写入目标地址信息,然后通过0xCFC地址读数据,就可以获得这个配置信息。这里的写和读,都使用的是x86所特有的io指令。

写入0xCF8的目标地址信息,是一种包括了总线号,设备号,功能号和配置寄存器地址的综合信息。

1 总线对设备的扫描和管理

计算机系统的设备是如何被发现的?对PCI总线上的设备来说,这是总线扫描所得到的结果(非PCI总线各有各自的扫描方式)。每个PCI设备有一个总线号,一个设备号,一个功能号标识。Pci规范允许一个系统最多拥有256条总线,每条总线最多可以带32个设备,每个设备可以是最多8个功能的多功能板。Pci扫描就是对单条总线的地址范围进行扫描。根据前面关于pci配置信息的知识,如果某个地址存在设备,那么在0xCF8写入目标地址信息,就可以从0xCFC读到设备的信息。包括设备的io端口和io内存,设备的中断号和DMA信息。对每个读到的pci设备,都要为它创建设备对象。

2 总线对驱动和设备的管理

当设备插入计算机系统时,可以找到自己的驱动开始工作。而升级了设备驱动,也能找到适合的设备,自动产生设备。这些功能其实就是pci总线结构完成的。通过代码分析来了解一下:

代码清单3-1  pci_register_driver 

int __pci_register_driver(struct pci_driver *drv, struct module *owner)

{

int error;

      /*设置驱动的总线类型和参数*/

/* initialize common driver fields */

drv->driver.name = drv->name;

drv->driver.bus = &pci_bus_type;

drv->driver.owner = owner;

drv->driver.kobj.ktype = &pci_driver_kobj_type;

spin_lock_init(&drv->dynids.lock);

INIT_LIST_HEAD(&drv->dynids.list);

/* register with core */

error = driver_register(&drv->driver);

if (!error)

error = pci_create_newid_file(drv);

return error;

} <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

注册一个pci驱动,其实就是调用了__pci_register_driver函数。首先设置了驱动的总线类型为pci,然后调用driver_register登记。

代码清单3-2  driver_register

int driver_register(struct device_driver * drv)

{

klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);

init_completion(&drv->unloaded);

return bus_add_driver(drv);

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

实际是调用了bus_add_driverbus_add_driver中真正起作用的是driver_attach这个函数。

代码清单3-3  

void driver_attach(struct device_driver * drv)

{

bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

} <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

这里就清楚了,driver_attach实际上把总线上面的所有设备都遍历了一遍,通过__driver_attach函数判断驱动和设备是否匹配。而__driver_attach实际是调用了driver_probe_device来检查设备和驱动的匹配关系。

代码清单3-4  

int driver_probe_device(struct device_driver * drv, struct device * dev)

{

int ret = 0;

       /*总线定义了match函数,通过match函数判断是否匹配*/

if (drv->bus->match && !drv->bus->match(dev, drv))

goto Done;

pr_debug("%s: Matched Device %s with Driver %s\n",

 drv->bus->name, dev->bus_id, drv->name);

dev->driver = drv;

       /*匹配,调用probe函数*/

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if (ret) {

dev->driver = NULL;

goto ProbeFailed;

}

} else if (drv->probe) {

ret = drv->probe(dev);

if (ret) {

dev->driver = NULL;

goto ProbeFailed;

}

}

device_bind_driver(dev);

ret = 1;

pr_debug("%s: Bound Device %s to Driver %s\n",

 drv->bus->name, dev->bus_id, drv->name);

goto Done;

 Done:

return ret;

} <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

判断驱动和设备是否匹配,首先是通过注册的match函数判断。通常情况,匹配是通过驱动里面包含的id表和扫描设备发现的id比较,如果相同,则说明驱动和设备是适合的。当匹配通过后,调用驱动里面的probe函数。这个函数往往用来继续对设备做进一步的检测工作。在后面块设备的驱动可以看到具体的用法。

4.1.3 设备中断

Cpu虚拟化一节已经讨论了设备中断的处理。

4.2 虚拟化的设备驱动和总线

半虚拟化结构的xen提出了虚拟设备的架构。Xen的虚拟设备架构采用前后端分离的设备驱动结构。虚拟设备驱动包含两个部分:domU中的前段设备驱动(fronted)和dom0中的后端设备驱动。后端设备驱动可以访问真实的硬件设备。

前端设备驱动从Guest OS接收io请求,然后将io请求转发到后端,而后端接收到前端转发的设备请求后,检查请求是否合法,然后通过本地的设备驱动访问真实的硬件设备。Io完成后,后端设备驱动通知前端设备驱动已经准备就绪,然后前端驱动向Guest OS报告io操作完成。

Xen的半虚拟化架构中,同样需要一种机制来发现设备,连接设备和驱动,自动匹配设备和驱动。而且和linux不同的是,因为前端设备和后端设备是联动的关系,当某一方设备变动的时候,还必须通知另一方设备的变动情况。为了完成这个工作,xen提供了一条虚拟总线xenbus来管理所有的虚拟设备和驱动。Xen系统的所有虚拟设备都要注册到xenbusPci总线也是作为一个设备注册到xenbus ,通过注册的pci总线,执行扫描动作可以产生所有的pci设备。而所有的虚拟驱动也都要注册到xenbus,从而可以自动完成虚拟设备和驱动的匹配。

4.2.1 Pci前端注册和扫描

代码清单3-5  pcifront_init

static int __init pcifront_init(void)

{

if (!is_running_on_xen())

return -ENODEV;

return xenbus_register_frontend(&xenbus_pcifront_driver);

}<object data="data:application/x-silverlight-2," 

Pcifront作为一个前端驱动注册到xenbus。注册驱动后,会自动调用驱动的probe函数,完成初始化后,把设备状态改为初始完成状态。通过xenbus,pci后端检测到前端变化,将信息同步后,将状态改为connected。前端此时要调用pcifront_try_connect完成扫描。

代码清单3-6  

static int pcifront_try_connect(struct pcifront_device *pdev)

{

int err = -EFAULT;

int i, num_roots, len;

char str[64];

unsigned int domain, bus;

spin_lock(&pdev->dev_lock);

/* Only connect once */

if (xenbus_read_driver_state(pdev->xdev->nodename) !=

    XenbusStateInitialised)

goto out;

       /*检查状态,保证只连接一次*/

err = pcifront_connect(pdev);

if (err) {

xenbus_dev_fatal(pdev->xdev, err,

 "Error connecting PCI Frontend");

goto out;

}

       

err = xenbus_scanf(XBT_NIL, pdev->xdev->otherend,

   "root_num", "%d", &num_roots);

if (err == -ENOENT) {

xenbus_dev_error(pdev->xdev, err,

 "No PCI Roots found, trying 0000:00");

err = pcifront_scan_root(pdev, 0, 0);

num_roots = 0;

} else if (err != 1) {

if (err == 0)

err = -EINVAL;

xenbus_dev_fatal(pdev->xdev, err,

 "Error reading number of PCI roots");

goto out;

}

for (i = 0; i < num_roots; i++) {

len = snprintf(str, sizeof(str), "root-%d", i);

if (unlikely(len >= (sizeof(str) - 1))) {

err = -ENOMEM;

goto out;

}

                 /*获得pci的域号和总线号*/

err = xenbus_scanf(XBT_NIL, pdev->xdev->otherend, str,

   "%x:%x", &domain, &bus);

if (err != 2) {

if (err >= 0)

err = -EINVAL;

xenbus_dev_fatal(pdev->xdev, err,

 "Error reading PCI root %d", i);

goto out;

}

                 /*扫描pci总线*/

err = pcifront_scan_root(pdev, domain, bus);

if (err) {

xenbus_dev_fatal(pdev->xdev, err,

 "Error scanning PCI root %04x:%02x",

 domain, bus);

goto out;

}

}

err = xenbus_switch_state(pdev->xdev, XenbusStateConnected);

if (err)

goto out;

      out:

spin_unlock(&pdev->dev_lock);

return err;

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

获得后端设置的pci域号和pci总线号后,调用pcifront_scan_root来扫描设备。

<object data="data:application/x-silverlight-2,"

代码清单3-7  pcifront_scan_root

int pcifront_scan_root(struct pcifront_device *pdev,

       unsigned int domain, unsigned int bus)

{

struct pci_bus *b;

struct pcifront_sd *sd = NULL;

struct pci_bus_entry *bus_entry = NULL;

int err = 0;

bus_entry = kmalloc(sizeof(*bus_entry), GFP_KERNEL);

sd = kmalloc(sizeof(*sd), GFP_KERNEL);

if (!bus_entry || !sd) {

err = -ENOMEM;

goto err_out;

}

pcifront_init_sd(sd, domain, pdev);

b = pci_scan_bus_parented(&pdev->xdev->dev, bus,

  &pcifront_bus_ops, sd);

if (!b) {

dev_err(&pdev->xdev->dev,

"Error creating PCI Frontend Bus!\n");

err = -ENOMEM;

goto err_out;

}

bus_entry->bus = b;

list_add(&bus_entry->list, &pdev->root_buses);

/* Claim resources before going "live" with our devices */

pci_walk_bus(b, pcifront_claim_resource, pdev);

pci_bus_add_devices(b);

return 0;

      err_out:

kfree(bus_entry);

kfree(sd);

return err;

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

pci_scan_bus_parented就是对pci总线的扫描,扫描之后,产生的pci设备就连接到总线的链表头,从而通过总线可以遍历pci设备。而pci_walk_bus要把设备的资源(io端口,io内存)全都注册到系统内。

4.3 虚拟化的块设备

硬盘等块设备采用了前后端的设备驱动架构。对原生的驱动比较,采用半虚拟化之后的驱动分成了两部分。在domU里面运行的是前端驱动,而dom0里面运行后端驱动。 

后端驱动和前端一一对应。当前端驱动状态改变的时候,就会触发事件,通知后端驱动。当后端驱动完成事务处理后,通过改变状态,就可以触发事件,通知前端驱动。所以前端驱动和后端驱动是互相呼应,共同完成io请求。

4.3.1 块设备的前端驱动

前端驱动定义了一个xenbus的驱动类型。如清单所示:

代码清单3-8  blkfront

static struct xenbus_driver blkfront = {

.name = "vbd",

.owner = THIS_MODULE,

.ids = blkfront_ids,

.probe = blkfront_probe,

.remove = blkfront_remove,

.resume = blkfront_resume,

.otherend_changed = backend_changed,

根据前面总线的分析,这个驱动要注册到xenbus总线。如果和xenbus扫描出来的设备能匹配,那么要调用驱动提供的probe函数,做进一步的初始化。

代码清单3-9  虚拟驱动例子

static int blkfront_probe(struct xenbus_device *dev,

  const struct xenbus_device_id *id)

{

int err, vdevice, i;

struct blkfront_info *info;

/* FIXME: Use dynamic device id if this is not set. */

err = xenbus_scanf(XBT_NIL, dev->nodename,

   "virtual-device", "%i", &vdevice);

if (err != 1) {

xenbus_dev_fatal(dev, err, "reading virtual-device");

return err;

}

info = kzalloc(sizeof(*info), GFP_KERNEL);

if (!info) {

xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure");

return -ENOMEM;

}

info->xbdev = dev;

info->vdevice = vdevice;

info->connected = BLKIF_STATE_DISCONNECTED;

INIT_WORK(&info->work, blkif_restart_queue, (void *)info);

for (i = 0; i < BLK_RING_SIZE; i++)

info->shadow[i].req.id = i+1;

info->shadow[BLK_RING_SIZE-1].req.id = 0x0fffffff;

/* Front end dir is a number, which is used as the id. */

info->handle = simple_strtoul(strrchr(dev->nodename,'/')+1, NULL, 0);

dev->dev.driver_data = info;

       /*连接后端*/

err = talk_to_backend(dev, info);

if (err) {

kfree(info);

dev->dev.driver_data = NULL;

return err;

}

return 0;

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

这个函数主要是申请一个blkfront_info类型的数据结构。这个数据结构是个容器,既包括块设备有关的请求队列和gendisk数据结构,也包括和虚拟化有关的ring结构,和grantbable结构。

初始化之后,调用talk_to_backend来和后端设备连接。

代码清单3-10  虚拟驱动例子

static int talk_to_backend(struct xenbus_device *dev,

   struct blkfront_info *info)

{

const char *message = NULL;

struct xenbus_transaction xbt;

int err;

       /*ring是前端和后端通讯的一种方式*/

/* Create shared ring, alloc event channel. */

err = setup_blkring(dev, info);

if (err)

goto out;

again:

       /*启动一个xenbus事务*/

err = xenbus_transaction_start(&xbt);

if (err) {

xenbus_dev_fatal(dev, err, "starting transaction");

goto destroy_blkring;

}

       /*输出信息,在后端可以看到输出的信息*/

err = xenbus_printf(xbt, dev->nodename,

    "ring-ref","%u", info->ring_ref);

if (err) {

message = "writing ring-ref";

goto abort_transaction;

}

err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",

    irq_to_evtchn_port(info->irq));

if (err) {

message = "writing event-channel";

goto abort_transaction;

}

err = xenbus_printf(xbt, dev->nodename, "protocol", "%s",

    XEN_IO_PROTO_ABI_NATIVE);

if (err) {

message = "writing protocol";

goto abort_transaction;

}

err = xenbus_transaction_end(xbt, 0);

if (err) {

if (err == -EAGAIN)

goto again;

xenbus_dev_fatal(dev, err, "completing transaction");

goto destroy_blkring;

}

       /*改变前端状态为初始化完成*/ 

xenbus_switch_state(dev, XenbusStateInitialised);

return 0;

 abort_transaction:

xenbus_transaction_end(xbt, 1);

if (message)

xenbus_dev_fatal(dev, err, "%s", message);

 destroy_blkring:

blkif_free(info, 0);

 out:

return err;

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

talk_to_backend主要是建立和后端的通讯机制,也就是xenbus_transaction。这种通讯机制是基于xen提供的机制xen store实现的。xenbus_transaction实现了一种类似文件系统的读写接口。建立通讯机制后,就可以调用xenbus_printf写入信息,而后端驱动就可以读取这些信息。

然后前端驱动把状态改为初始化完成,等待后端驱动的回应。

从虚拟化的逻辑来说,此时控制逻辑转入后端驱动。当后端驱动完成需要的处理,将状态改为“connected"后,前端驱动将调用connect函数,完成虚拟化块设备的整个初始化工作。后端掌管的是物理设备,只有后端才能看到设备的真正信息,所以后端要完成设备的物理信息。比如块设备的扇区数目,扇区大小等等。

  

代码清单3-11  connect

static void connect(struct blkfront_info *info)

{

unsigned long long sectors;

unsigned long sector_size;

unsigned int binfo;

int err;

       ............................................

       /**获得设备的物理信息,扇区,扇区大小等等/

err = xenbus_gather(XBT_NIL, info->xbdev->otherend,

    "sectors", "%Lu", §ors,

    "info", "%u", &binfo,

    "sector-size", "%lu", §or_size,

    NULL);

err = xenbus_gather(XBT_NIL, info->xbdev->otherend,

    "feature-barrier", "%lu", &info->feature_barrier,

    NULL);

err = xlvbd_add(sectors, info->vdevice, binfo, sector_size, info);

       /*切换状态为connected*/

(void)xenbus_switch_state(info->xbdev, XenbusStateConnected);

/* Kick pending requests. */

spin_lock_irq(&blkif_io_lock);

info->connected = BLKIF_STATE_CONNECTED;

kick_pending_request_queues(info);

spin_unlock_irq(&blkif_io_lock);

      /*把创建的磁盘设备加入到系统*/ 

add_disk(info->gd);

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

Connect做的事情是根据后端写入的物理信息,来创建磁盘设备,设置设备的参数。最重要的是,要截获io请求,转入虚拟化的处理过程。

Linux的文件读写,是以请求队列的形式发送给块设备。每个块设备都有自己的请求队列处理函数。如果改写这个函数,就可以截获linux的io请求。

xlvbd_add要调用虚拟块设备的核心函数来完成块设备创建,初始化的工作。虚拟块设备的函数中,最重要的是xlvbd_alloc_gendisk。

代码清单3-12  虚拟驱动例子

xlvbd_alloc_gendisk(int minor, blkif_sector_t capacity, int vdevice,

    u16 vdisk_info, u16 sector_size,

    struct blkfront_info *info)

{

      ...............................

       /*分配一个gendisk数据结构*/

gd = alloc_disk(nr_minors);

       ................................................

       /*设置gd的主设备号,私有数据和磁盘容量*/

gd->major = mi->major;

gd->first_minor = minor;

gd->fops = &xlvbd_block_fops;

gd->private_data = info;

gd->driverfs_dev = &(info->xbdev->dev);

set_capacity(gd, capacity);

       /*注册虚拟块设备的io队列处理函数*/

if (xlvbd_init_blk_queue(gd, sector_size)) {

del_gendisk(gd);

goto out;

}

if (vdisk_info & VDISK_CDROM)

gd->flags |= GENHD_FL_CD;

       return 0;

}

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

这里首先是申请一个gendisk设备数据结构,然后设置设备参数。注意,这里的参数是后端驱动写入的。

xlvbd_init_blk_queue的作用就是注册虚拟块设备的请求队列处理函数,这个函数是do_blkif_request。所有发送到块设备的io都首先经过这个函数。

do_blkif_request这个函数要把所有接收的io请求都转发到后端驱动。这里就不分析了。

4.3.2 块设备后端驱动

对后端驱动的分析,重点在后端对前端驱动的呼应和配合。首先分析后端驱动对前端状态改变的处理函数。

代码清单3-13  虚拟驱动例子

static void frontend_changed(struct xenbus_device *dev,

     enum xenbus_state frontend_state)

{

struct backend_info *be = dev->dev.driver_data;

int err;

DPRINTK("%s", xenbus_strstate(frontend_state));

switch (frontend_state) {

case XenbusStateInitialising:

break;

case XenbusStateInitialised:

case XenbusStateConnected:

                 ..........................

err = connect_ring(be);

update_blkif_status(be->blkif);

break;

case XenbusStateClosing:

blkif_disconnect(be->blkif);

xenbus_switch_state(dev, XenbusStateClosing);

break;

}

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

简化后端的状态处理函数,当前端状态变为初始化完成,后端要调用connect_ring来连接前端定义的ring,然后update_blkif_status来写入设备信息和更新状态。

代码清单3-14  虚拟驱动例子

static void update_blkif_status(blkif_t *blkif)

int err;

char name[TASK_COMM_LEN];

       .........................................   

/* Attempt to connect: exit if we fail to. */

connect(blkif->be);

err = blkback_name(blkif, name);

blkif->xenblkd = kthread_run(blkif_schedule, blkif, name);

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

实际调用的是connect来写入设备信息。

代码清单3-15  connect

static void connect(struct backend_info *be)

{

struct xenbus_transaction xbt;

int err;

struct xenbus_device *dev = be->dev;

       /*写入块设备扇区*/

err = xenbus_printf(xbt, dev->nodename, "sectors", "%llu",

    vbd_size(&be->blkif->vbd));

/* FIXME: use a typename instead */

err = xenbus_printf(xbt, dev->nodename, "info", "%u",

    vbd_info(&be->blkif->vbd));

       /*写入扇区大小*/

err = xenbus_printf(xbt, dev->nodename, "sector-size", "%lu",

    vbd_secsize(&be->blkif->vbd));

 

       /*设置状态为connected*/

err = xenbus_switch_state(dev, XenbusStateConnected);

       

return;

}<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"

后端要写入块设备的扇区数目和扇区大小,以及node的名字。在前面块设备前端驱动的分析中,要通过connect函数来读入扇区数目和扇区大小来完成块设备初始化。读入的信息就是在这是由后端驱动写入的。

本节的分析只涉及块设备的创建和初始化。至于块设备对文件io的处理,以及前端和后端联动处理io的过程,读者可以自行分析一下。

 


1 0
原创粉丝点击