ubuntu驱动开发吧 关注:25贴子:177
  • 5回复贴,共1
第1章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
同样是读书,读小说可以行云流水,读完后心情舒畅,意犹未尽;读电脑书却举步艰难,读完后目光呆滞,也是意犹未尽,只不过未尽的是痛苦的回忆。
研究证明,痛苦的记忆比快乐的更难忘记,因此电脑书中的内容比小说记得持久。
而这套教程的目的是要打破这种状况,以至于读者在忘记小说内容忘记本文。
在这套教程中,我们通过写一个建立在内存中的块设备驱动,来学习linux内核和相关设备驱动知识。
选择写块设备驱动的原因是:
1:容易上手
2:可以牵连出更多的内核知识
3:像本文这样的块设备驱动教程不多,所以需要一个
好吧,扯淡到此结束,我们开始写了。
本章的目的用尽可能最简单的方法写出一个能用的块设备驱动。
所谓的能用,是指我们可以对这个驱动生成的块设备进行mkfs,mount和读写文件。
为了尽可能简单,这个驱动的规模不是1000行,也不是500行,而是100行以内。
这里插一句,我们不打算在这里介绍如何写模块,理由是介绍的文章已经满天飞舞了。
如果你能看得懂、并且成功地编译、运行了这段代码,我们认为你已经达到了本教程的入学资格,
当然,如果你不幸的卡在这段代码中,那么请等到搞定它以后再往下看:
mod.c:
#include <linux/module.h>
static int __init init_base(void)
{
printk("----Hello. World----\n");
return 0;
}
static void __exit exit_base(void)
{
printk("----Bye----\n");
}
module_init(init_base);
module_exit(exit_base);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Zhao Lei");
MODULE_DESCRIPTION("For test");
Makefile:
obj-m := mod.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
rm -rf Module.markers modules.order Module.symvers
好了,这里我们假定你已经搞定上面的最简单的模块了,懂得什么是看模块,以及简单模块的编写、编译、加载和卸载。
还有就是,什么是块设备,什么是块设备驱动,这个也请自行google吧,因为我们已经迫不及待要写完程序下课。
为了建立一个可用的块设备,我们需要做......1件事情:
1:用add_disk()函数向系统中添加这个块设备
添加一个全局的
static struct gendisk *simp_blkdev_disk;
然后申明模块的入口和出口:
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);
然后在入口处添加这个设备、出口处私房这个设备:
static int __init simp_blkdev_init(void)
{
add_disk(simp_blkdev_disk);
return 0;
}
static void __exit simp_blkdev_exit(void)
{
del_gendisk(simp_blkdev_disk);
}
当然,在添加设备之前我们需要申请这个设备的资源,这用到了alloc_disk()函数,因此模块入口函数simp_blkdev_init(void)应该是:
static int __init simp_blkdev_init(void)
{
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
return ret;
}
还有别忘了在卸载模块的代码中也加一个行清理函数:
put_disk(simp_blkdev_disk);
还有就是,设备有关的属性也是需要设置的,因此在alloc_disk()和add_disk()之间我们需要:
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
simp_blkdev_disk->major = ?1;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = ?2;
simp_blkdev_disk->queue = ?3;
set_capacity(simp_blkdev_disk, ?4);
SIMP_BLKDEV_DISKNAME其实是这个块设备的名称,为了绅士一些,我们把它定义成宏了:
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
这里又引出了4个问号。(天哪,是不是有种受骗的感觉,像是陪老婆去做头发)
第1个问号:
每个设备需要对应的主、从驱动号。
我们的设备当然也需要,但很明显我不是脑科医生,因此跟写linux的那帮疯子不熟,得不到预先为我保留的设备号。
还有一种方法是使用动态分配的设备号,但在这


IP属地:安徽1楼2015-03-03 23:02回复
    中:@ 不:@ 打算进行深入的讲解,但我们必须知道的是:
    1:I/O调度器把排序后的访问需求通过request_queue结构传递给块设备驱动程序处理
    2:我们的驱动程序需要设置一个request_queue结构
    申请request_queue结构的函数是blk_init_queue(),而调用blk_init_queue()函数时需要传入一个函数的地址,这个函数担负着处理对块设备数据的请求。
    因此我们需要做的就是:
    1:实现一个static void simp_blkdev_do_request(struct request_queue *q)函数。
    2:加入一个全局变量,指向块设备需要的请求队列:
    static struct request_queue *simp_blkdev_queue;
    3:在加载模块时用simp_blkdev_do_request()函数的地址作参数调用blk_init_queue()初始化一个请求队列:
    simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
    if (!simp_blkdev_queue) {
    ret = -ENOMEM;
    goto err_init_queue;
    }
    4:卸载模块时把simp_blkdev_queue还回去:
    blk_cleanup_queue(simp_blkdev_queue);
    5:在?3的位置填上simp_blkdev_queue。
    第4个问号:
    这个还好,比前面的简单多了,这里需要设置块设备的大小。
    块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。
    当然,在把字节为单位的大小转换为以扇区为单位时,我们需要除以512,或者右移9位可能更快一些。
    同样,我们试图把这一步也做得绅士一些,因此使用宏定义了块设备的大小,目前我们定为16M:
    #define SIMP_BLKDEV_BYTES (16*1024*1024)
    然后在?4的位置填上SIMP_BLKDEV_BYTES>>9。
    看到这里,是不是有种身陷茫茫大海的无助感?并且一波未平,一波又起,在搞定这4个问号的同时,居然又引入了simp_blkdev_do_request函数!
    当然,如果在身陷茫茫波涛中时你认为到处都是海,因此绝望,那么恭喜你可以不必挨到65岁再退休;
    反之,如果你认为到处都是没有三聚氰胺鲜鱼,并且随便哪个方向都是岸时,那么也恭喜你,你可以活着回来继续享受身为纳税人的荣誉。
    为了理清思路,我们把目前为止涉及到的代码整理出来:
    #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
    #define SIMP_BLKDEV_DISKNAME "simp_blkdev"
    #define SIMP_BLKDEV_BYTES (16*1024*1024)
    static struct request_queue *simp_blkdev_queue;
    static struct gendisk *simp_blkdev_disk;
    static void simp_blkdev_do_request(struct request_queue *q);
    struct block_device_operations simp_blkdev_fops = {
    .owner = THIS_MODULE,
    };
    static int __init simp_blkdev_init(void)
    {
    int ret;
    simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
    if (!simp_blkdev_queue) {
    ret = -ENOMEM;
    goto err_init_queue;
    }
    simp_blkdev_disk = alloc_disk(1);
    if (!simp_blkdev_disk) {
    ret = -ENOMEM;
    goto err_alloc_disk;
    }
    strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
    simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
    simp_blkdev_disk->first_minor = 0;
    simp_blkdev_disk->fops = &simp_blkdev_fops;
    simp_blkdev_disk->queue = simp_blkdev_queue;
    set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    add_disk(simp_blkdev_disk);
    return 0;
    err_alloc_disk:
    blk_cleanup_queue(simp_blkdev_queue);
    err_init_queue:
    return ret;
    }
    static void __exit simp_blkdev_exit(void)
    {
    del_gendisk(simp_blkdev_disk);
    put_disk(simp_blkdev_disk);
    blk_cleanup_queue(simp_blkdev_queue);
    }
    module_init(simp_blkdev_init);
    module_exit(simp_blkdev_exit);
    剩下部分的不多了,真的不多了。请相信我,因为我不在质监局上班。
    我写的文章诚实可靠,并且不拿你纳税的钱。


    IP属地:安徽2楼2015-03-03 23:03
    回复
      们还有一个最重要的函数需要实现,就是负责处理块设备请求的simp_blkdev_do_request()。
      首先我们看看究竟把块设备的数据以什么方式放在内存中。
      毕竟这是在第1章,因此我们将使用最simple的方式实现,也就是,数组。
      我们在全局代码中定义:
      unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
      对驱动程序来说,这个数组看起来大了一些,如果不幸被懂行的人看到,将100%遭到最无情、最严重的鄙视。
      而我们却从极少数公仆那里学到了最有效的应对之策,那就是:无视他,然后把他定为成“不明真相的群众”。
      然后我们着手实现simp_blkdev_do_request。
      这里介绍elv_next_request()函数,原型是:
      struct request *elv_next_request(struct request_queue *q);
      用来从一个请求队列中拿出一条请求(其实严格来说,拿出的可能是请求中的一段)。
      随后的处理请求本质上是根据rq_data_dir(req)返回的该请求的方向(读/写),把块设备中的数据装入req->buffer、或是把req->buffer中的数据写入块设备。
      刚才已经提及了与request结构相关的rq_data_dir()宏和.buffer成员,其他几个相关的结构成员和函数是:
      request.sector:请求的开始磁道
      request.current_nr_sectors:请求磁道数
      end_request():结束一个请求,第2个参数表示请求处理结果,成功时设定为1,失败时设置为0或者错误号。
      因此我们的simp_blkdev_do_request()函数为:
      static void simp_blkdev_do_request(struct request_queue *q)
      {
      struct request *req;
      while ((req = elv_next_request(q)) != NULL) {
      if ((req->sector + req->current_nr_sectors) << 9
      > SIMP_BLKDEV_BYTES) {
      printk(KERN_ERR SIMP_BLKDEV_DISKNAME
      ": bad request: block=%llu, count=%u\n",
      (unsigned long long)req->sector,
      req->current_nr_sectors);
      end_request(req, 0);
      continue;
      }
      switch (rq_data_dir(req)) {
      case READ:
      memcpy(req->buffer,
      simp_blkdev_data + (req->sector << 9),
      req->current_nr_sectors << 9);
      end_request(req, 1);
      break;
      case WRITE:
      memcpy(simp_blkdev_data + (req->sector << 9),
      req->buffer, req->current_nr_sectors << 9);
      end_request(req, 1);
      break;
      default:
      /* No default because rq_data_dir(req) is 1 bit */
      break;
      }
      }
      }
      函数使用elv_next_request()遍历struct request_queue *q中使用struct request *req表示的每一段,首先判断这个请求是否超过了我们的块设备的最大容量,
      然后根据请求的方向rq_data_dir(req)进行相应的请求处理。由于我们使用的是指简单的数组,因此请求处理仅仅是2条memcpy。
      memcpy中也牵涉到了扇区号到线性地址的转换操作,我想对坚持到这里的读者来说,这个操作应该不需要进一步解释了。


      IP属地:安徽3楼2015-03-03 23:03
      回复
        编码到此结束,然后我们试试这个程序:
        首先编译:
        # make
        make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step1 modules
        make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
        CC [M] /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.o
        Building modules, stage 2.
        MODPOST
        CC /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.mod.o
        LD [M] /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.ko
        make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
        #
        加载模块
        # insmod simp_blkdev.ko
        #
        用lsmod看看。
        这里我们注意到,该模块的Used by为0,因为它既没有被其他模块使用,也没有被mount。
        # lsmod
        Module Size Used by
        simp_blkdev 16784008 0
        ...
        #
        如果当前系统支持udev,在调用add_disk()函数时即插即用机制会自动为我们在/dev/目录下建立设备文件。
        设备文件的名称为我们在gendisk.disk_name中设置的simp_blkdev,主、从设备号也是我们在程序中设定的72和0。
        如果当前系统不支持udev,那么很不幸,你需要自己用mknod /dev/simp_blkdev b 72 0来创建设备文件了。
        # ls -l /dev/simp_blkdev
        brw-r----- 1 root disk 72, 0 11-10 18:13 /dev/simp_blkdev
        #
        在块设备中创建文件系统,这里我们创建常用的ext3。
        当然,作为通用的块设备,创建其他类型的文件系统也没问题。
        # mkfs.ext3 /dev/simp_blkdev
        mke2fs 1.39 (29-May-2006)
        Filesystem label=
        OS type: Linux
        Block size=1024 (log=0)
        Fragment size=1024 (log=0)
        4096 inodes, 16384 blocks
        819 blocks (5.00%) reserved for the super user
        First data block=1
        Maximum filesystem blocks=16777216
        2 block groups
        8192 blocks per group, 8192 fragments per group
        2048 inodes per group
        Superblock backups stored on blocks:
        8193
        Writing inode tables: done
        Creating journal (1024 blocks): done
        Writing superblocks and filesystem accounting information: done
        This filesystem will be automatically checked every 38 mounts or
        180 days, whichever comes first. Use tune2fs -c or -i to override.
        #
        如果这是第一次使用,建议创建一个目录用来mount这个设备中的文件系统。
        当然,这不是必需的。如果你对mount之类的用法很熟,你完全能够自己决定在这里干什么,甚至把这个设备mount成root。
        # mkdir -p /mnt/temp1
        #
        把建立好文件系统的块设备mount到刚才建立的目录中
        # mount /dev/simp_blkdev /mnt/temp1
        #
        看看现在的mount表
        # mount
        ...
        /dev/simp_blkdev on /mnt/temp1 type ext3 (rw)
        #
        看看现在的模块引用计数,从刚才的0变成1了,
        原因是我们mount了。
        # lsmod
        Module Size Used by
        simp_blkdev 16784008 1
        ...
        #
        看看文件系统的内容,有个mkfs时自动建立的lost+found目录。
        # ls /mnt/temp1
        lost+found
        #
        随便拷点东西进去
        # cp /etc/init.d/* /mnt/temp1
        #
        再看看
        # ls /mnt/temp1
        acpid conman functions irqbalance mdmpd NetworkManagerDispatcher rdisc sendmail winbind
        anacron cpuspeed gpm kdump messagebus nfs readahead_early setroubleshoot wpa_supplicant
        apmd crond haldaemon killall microcode_ctl nfslock readahead_later single xfs
        atd cups halt krb524 multipathd nscd restorecond smartd xinetd
        auditd cups-config-daemon hidd kudzu netconsole ntpd rhnsd smb ypbind
        autofs dhcdbd ip6tables lost+found netfs pand rpcgssd sshd yum-updatesd
        avahi-daemon dund ipmi lvm2-monitor netplugd pcscd rpcidmapd syslog
        avahi-dnsconfd firstboot iptables mcstrans network portmap rpcsvcgssd vmware
        bluetooth frecord irda mdmonitor NetworkManager psacct saslauthd vncserver
        #
        现在这个块设备的使用情况是
        # df
        文件系统 1K-块 已用 可用 已用% 挂载点
        ...
        /dev/simp_blkdev 15863 1440 13604 10% /mnt/temp1
        #
        再全删了玩玩
        # rm -rf /mnt/temp1/*
        #
        看看删完了没有
        # ls /mnt/temp1
        #
        好了,大概玩够了,我们把文件系统umount掉
        # umount /mnt/temp1
        #
        模块的引用计数应该还原成0了吧
        # lsmod
        Module Size Used by
        simp_blkdev 16784008 0
        ...
        #
        最后一步,移除模块
        # rmmod simp_blkdev
        #


        IP属地:安徽4楼2015-03-03 23:04
        回复
          然后我们在这两个分区中创建文件系统
          # mkfs.ext3 /dev/simp_blkdev1
          mke2fs 1.39 (29-May-2006)
          Filesystem label=
          OS type: Linux
          Block size=1024 (log=0)
          Fragment size=1024 (log=0)
          2000 inodes, 8000 blocks
          400 blocks (5.00%) reserved for the super user
          First data block=1
          Maximum filesystem blocks=8388608
          1 block group
          8192 blocks per group, 8192 fragments per group
          2000 inodes per group
          Writing inode tables: done
          Creating journal (1024 blocks): done
          Writing superblocks and filesystem accounting information: done
          This filesystem will be automatically checked every 27 mounts or
          180 days, whichever comes first. Use tune2fs -c or -i to override.
          # mkfs.ext3 /dev/simp_blkdev2
          mke2fs 1.39 (29-May-2006)
          Filesystem label=
          OS type: Linux
          Block size=1024 (log=0)
          Fragment size=1024 (log=0)
          2008 inodes, 8032 blocks
          401 blocks (4.99%) reserved for the super user
          First data block=1
          Maximum filesystem blocks=8388608
          1 block group
          8192 blocks per group, 8192 fragments per group
          2008 inodes per group
          Writing inode tables: done
          Creating journal (1024 blocks): done
          Writing superblocks and filesystem accounting information: done
          This filesystem will be automatically checked every 23 mounts or
          180 days, whichever comes first. Use tune2fs -c or -i to override.
          #
          然后mount设两个设备:
          # mount /dev/simp_blkdev1 /mnt/temp1
          # mount /dev/simp_blkdev2 /mnt/temp2
          #
          看看结果:
          # mount
          /dev/hda1 on / type ext3 (rw)
          proc on /proc type proc (rw)
          sysfs on /sys type sysfs (rw)
          devpts on /dev/pts type devpts (rw,gid=5,mode=620)
          tmpfs on /dev/shm type tmpfs (rw)
          none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
          /dev/simp_blkdev1 on /mnt/temp1 type ext3 (rw)
          /dev/simp_blkdev2 on /mnt/temp2 type ext3 (rw)
          #
          然后读/写:
          # cp /etc/init.d/* /mnt/temp1/
          # cp /etc/passwd /mnt/temp2
          # ls /mnt/temp1/
          NetworkManager avahi-dnsconfd dund ipmi lost+found netfs portmap rpcsvcgssd vncserver
          NetworkManagerDispatcher bluetooth firstboot iptables lvm2-monitor netplugd psacct saslauthd winbind
          acpid capi functions irda mcstrans network rdisc sendmail wpa_supplicant
          anacron conman gpm irqbalance mdmonitor nfs readahead_early setroubleshoot xfs
          apmd cpuspeed haldaemon isdn mdmpd nfslock readahead_later single ypbind
          atd crond halt kdump messagebus nscd restorecond smartd yum-updatesd
          auditd cups hidd killall microcode_ctl ntpd rhnsd sshd
          autofs cups-config-daemon hplip krb524 multipathd pand rpcgssd syslog
          avahi-daemon dhcdbd ip6tables kudzu netconsole pcscd rpcidmapd vmware-tools
          # ls /mnt/temp2
          lost+found passwd
          #
          收尾工作:
          # umount /dev/temp1
          # umount /dev/temp2
          # rmmod simp_blkdev
          #
          看起来本:@章:@应该结束了,但为了耽误大家更多的时间,我们来回忆一下刚才出现的小麻烦。
          我们发现这块磁盘只有2个磁道,由于分区是以磁道为边界的,因此最大只能创建2个分区。
          不过谢天谢地,好歹我们能够证明我们的程序是支持“多个”分区的......尽管只有2个。
          那么为什么系统会认为我们的块设备只有2个磁道呢?其实这不怪系统,因为我们根本没有告诉系统我们的磁盘究竟有多少个磁道。
          因此系统只好去猜、猜、猜,结果就猜成2个磁道了。
          好吧,说的细节一些,传统的磁盘使用8个位表示盘面数、6个位表示每磁道扇区数、10个位表示磁道数,因此盘面、每磁道扇区、磁道的最大数值分别为255、63和1023。
          这也是传说中启动操作系统时的1024柱面(磁道)和硬盘容量8G限制的根源。
          现代磁盘采用线性寻址方式突破了这一限制,从本质上说,如果你的机器还没生锈,那么你的硬盘无论是内部结构还是访问方式都与常识中的盘面、每磁道扇区、磁道无关。
          但为了与原先的理解兼容,对于现代磁盘,我们在访问时还是假设它具有传统的结构。目前比较通用的假设是:所有磁盘具有最大数目的(也就是恒定的)盘面和每磁道扇区数,而磁盘大小与磁道数与成正比。
          因此,对于一块80G的硬盘,根据假设,这块磁盘的盘面和每磁道扇区数肯定是255和63,磁道数为:80*1024*1024*1024/512(字节每扇区)/255(盘面数)/63(每磁道扇区数)=10043(小数部分看作不完整的磁道被丢弃)。
          话归原题,在驱动程序中我们指定了磁盘大小为16M,共包含16*1024*1024/512=32768个扇区。假设这块磁盘具有最大盘面和每磁道扇区数后,它的磁道数就是:32768/255/63=2。
          我们看起开应该很happy,因为系统太看得起我们了,竟然把我们的块设备看成现代磁盘进行磁道数的换算处理。
          不过我们也可能unhappy,因为这造成磁盘最大只能被分成2个区。(至于为什么分区以磁道作为边界,可以想象一下磁盘的结构)
          但我们的磁盘只有区区16M啊,所以最好还是告诉系统我们的磁盘没有那么多的盘面数和每磁道扇区数,这将让磁道数来得多一些。


          IP属地:安徽11楼2015-03-03 23:10
          回复
            接下来的代码中,我们将要用到基树种的如下函数:
            void INIT_RADIX_TREE((struct radix_tree_root *root, gfp_t mask);
            用来初始化一个基树的结构,root是基树结构指针,mask是基树内部申请内存时使用的标志。
            int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item);
            用来往基树中插入一个指针,index是指针的索引,item是指针,将来可以通过index从基树中快速获得这个指针的值。
            void *radix_tree_delete(struct radix_tree_root *root, unsigned long index);
            用来根据索引从基树中删除一个指针,index是指针的索引。
            void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
            用来根据索引从基树中查找对应的指针,index是指针的索引。
            其实基树的功能不仅限于此,比如,还可以给指针设定标志,详情还是请去读linux/lib/radix-tree.c
            现在开始改造我们的代码:
            首先删除那个无耻的数组:
            unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
            然后引入它的替代者--一个基树结构:
            static struct radix_tree_root simp_blkdev_data;
            然后增加两个函数,用来申请和释放块设备的内存:
            申请内存的函数如下:
            int alloc_diskmem(void)
            {
            int ret;
            int i;
            void *p;
            INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
            for (i = 0; i < (SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) >> PAGE_SHIFT;
            i++) {
            p = (void *)__get_free_page(GFP_KERNEL);
            if (!p) {
            ret = -ENOMEM;
            goto err_alloc;
            }
            ret = radix_tree_insert(&simp_blkdev_data, i, p);
            if (IS_ERR_VALUE(ret))
            goto err_radix_tree_insert;
            }
            return 0;
            err_radix_tree_insert:
            free_page((unsigned long)p);
            err_alloc:
            free_diskmem();
            return ret;
            }
            先初始化基树结构,然后申请需要的每一个页面,按照每页面的次序作为索引,将指针插入基树。
            代码中的“>> PAGE_SHIFT”与“/ PAGE_SIZE”作用相同,
            if (不明白为什么要这样)
            do_google();
            释放内存的函数如下:
            void free_diskmem(void)
            {
            int i;
            void *p;
            for (i = 0; i < (SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) >> PAGE_SHIFT;
            i++) {
            p = radix_tree_lookup(&simp_blkdev_data, i);
            radix_tree_delete(&simp_blkdev_data, i);
            /* free NULL is safe */
            free_page((unsigned long)p);
            }
            }
            遍历每一个索引,得到页面的指针,释放页面,然后从基树中释放这个指针。
            由于alloc_diskmem()函数在中途失败时需要释放申请过的页面,因此我们把free_diskmem()函数设计成能够释放建立了一半的基树的形式。
            对于只建立了一半的基树而言,有一部分索引对应的指针还没来得及插入基树,对于不存在的索引,radix_tree_delete()函数会返回NULL,幸运的是free_page()函数能够忽略传入的NULL指针。
            因为alloc_diskmem()函数需要调用free_diskmem()函数,在代码中需要把free_diskmem()函数写在alloc_diskmem()前面,或者在文件头添加函数的声明。


            IP属地:安徽15楼2015-03-03 23:15
            回复