收藏 分享(赏)

6-6 块驱动.ppt

上传人:hyngb9260 文档编号:7801026 上传时间:2019-05-26 格式:PPT 页数:75 大小:372.50KB
下载 相关 举报
6-6 块驱动.ppt_第1页
第1页 / 共75页
6-6 块驱动.ppt_第2页
第2页 / 共75页
6-6 块驱动.ppt_第3页
第3页 / 共75页
6-6 块驱动.ppt_第4页
第4页 / 共75页
6-6 块驱动.ppt_第5页
第5页 / 共75页
点击查看更多>>
资源描述

1、第6章 嵌入式Linux驱动程序开发,块驱动blkdemo,2,字符设备与块设备,(1) 字符设备:提供连续的数据流,应用程序顺序读取,通常不支持随机存取,支持字节/字符读写。如调制解调器。 (2) 块设备:应用程序可随机访问设备数据,可自行确定读取数据的位置。如硬盘。数据的读写只能以块(通常是512B)的倍数进行,不支持字节/字符寻址。 两种设备本身并没用严格的区分,主要是字符设备和块设备驱动程序提供的访问接口(file I/O API)是不一样的。,3,有关块设备的数据结构,block_device_operations结构 gendisk结构 request_queue结构 buffer

2、_head结构 request结构,4,block_device_operations结构,struct block_device_operations int (*open) (struct inode *inode, struct file *filp); int (*release) (struct inode *inode, struct file *filp); int (*ioctl) (struct inode *inode, struct file *filp,unsigned command, unsigned long argument);int (*check_media

3、_change) (kdev_t dev);int (*revalidate) (kdev_t dev); struct module *owner; ;,block_device_operations 结构(在linux/fs.h中定义)的实例表示一个块设备,块设备在注册时要使用该结构的指针。,5,block_device_operations结构,open、release 、ioctl 与字符设备的对应方法相同。 check_media_change用于检查自从上次访问以来设备是否发生过变化。 revalidate在磁盘变化后,重新初始化驱动程序状态。 block_device_opera

4、tions 中没有read和write操作,所有块设备的I/O 通常由系统进行缓冲处理,用户进程不对这些设备执行直接的 I/O 操作。,6,block_device_operations结构,举例: struct block_device_operations blkdemo_bdops = open: blkdemo_open, release: blkdemo_release, ioctl: blkdemo_ioctl, check_media_change: blkdemo_change, revalidate: blkdemo_revalidate, ;,7,gendisk结构,支持分

5、区的块设备驱动程序须声明一个 struct gendisk 结构(在linux/genhd.h中定义),该结构描述了驱动程序所提供的磁盘布局。 内核维护这类结构的一个全局链表:static struct gendisk *gendisk_arrayMAX_BLKDEV;(在genhd.c中定义),8,gendisk结构,struct gendisk int major;const char *major_name;int minor_shift;int max_p;struct hd_struct *part;int *sizes;int nr_real;void *real_devices;

6、struct gendisk *next;struct block_device_operations *fops;devfs_handle_t *de_arr;char *flags; ;,主设备号,基本设备名,如“hd”是用来建立 /dev/hda1 和 /dev/hdb3 的基本名称,从次设备编号中得出驱动器编号时的位移数,其值和宏 DEVICE_NR(device)相同。,最大分区数,设备大小,单位KB,与全局blk_size数组相同,驱动程序负责分配和释放 sizes 数组,实际存在的设备个数,可用于保存私有数据,gendisk结构链表的指针,指向设备块操作结构的指针,设备分区表,9

7、,request_queue结构,每个块驱动至少有一个 I/O 请求队列,由一个 request_queue 结构表示,在 中声明。I/O请求队列包含了内核想在设备上完成的所有 I/O 操作,而I/O 操作的细节包含在request对象中。 当需要对设备进行读、写访问时,操作系统会对该设备文件节点调用block_read( ) 和block_write( )函数,这两函数会将请求发送到设备的请求队列中进行保存,并唤起该设备对应的底层操作函数。,10,request_queue结构,struct request_queue struct request_list rq2;struct list_

8、head queue_head;elevator_t elevator;request_fn_proc * request_fn;merge_request_fn * back_merge_fn;merge_request_fn * front_merge_fn;merge_requests_fn * merge_requests_fn;make_request_fn * make_request_fn;plug_device_fn * plug_device_fn;void * queuedata;struct tq_struct plug_tq;char plugged;char head

9、_active;spinlock_t queue_lock;wait_queue_head_t wait_for_request; ;,一组操作该队列的函数指针,类似 file_operations 结构,如驱动程序的 request 函数(即I/O请求处理函数),队列头,用于指向该设备的未处理请求,11,buffer_head结构,Linux 维护一个内存缓冲区,保存磁盘数据块的复本。 文件系统中执行的“磁盘”操作通常在缓存上进行,这样能够避免许多读盘操作,多个写入操作可以合并为单个物理的磁盘写操作。 内核通过 buffer_head 结构管理缓冲区缓存,每个数据缓冲区关联一个 buffer

10、_head,该结构包含有大量的成员,但大部分成员和驱动程序编写无关。,12,buffer_head结构(在linux/fs.h中声明),struct buffer_head struct buffer_head *b_next; /* Hash queue list */unsigned long b_blocknr; /* block number */unsigned short b_size; /* block size */unsigned short b_list; /* List that this buffer appears */kdev_t b_dev; /* device

11、(B_FREE = free) */atomic_t b_count; /* users using this block */kdev_t b_rdev; /* Real device */unsigned long b_state; /* buffer state bitmap (see above) */unsigned long b_flushtime; /* Time when (dirty) buffer should be written */struct buffer_head *b_next_free;/* lru/free list linkage */struct buf

12、fer_head *b_prev_free;/* doubly linked list of buffers */struct buffer_head *b_this_page;/* circular list of buffers in one page */struct buffer_head *b_reqnext; /* request queue */struct buffer_head *b_pprev; /* doubly linked list of hash-queue */char * b_data; /* pointer to data block */struct pag

13、e *b_page; /* the page this bh is mapped to */void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O completion */void *b_private; /* reserved for b_end_io */unsigned long b_rsector; /* Real buffer location on disk */wait_queue_head_t b_wait;struct inode * b_inode;struct list_head b_inode_bu

14、ffers; /* doubly linked list of inode dirty buffers */ ;,13,buffer_head结构,char *b_data:与该缓冲区头相关联的实际数据块 unsigned long b_size:b_data 所指向的数据块大小 kdev_t b_rdev:该缓冲区头所代表的数据块所在的设备 unsigned long b_rsector:该数据块在磁盘上的扇区编号 struct buffer_head *b_reqnext:指向请求队列中缓冲区头结构链表的指针 void (*b_end_io)(struct buffer_head *bh,

15、 int uptodate):函数指针,当该缓冲区上的 I/O 操作结束时调用该函数。bh 是缓冲区头本身, uptodate 在 I/O 成功时取非零值。,14,request结构,block_device_operations 中没有read和write 操作,所有块设备的 I/O 由系统进行缓冲处理,用户进程不会对这些设备执行直接的 I/O 操作。块驱动程序最终必须提供完成实际块 I/O 操作的机制。在 Linux 当中,用于这些 I/O 操作的方法称为“request(请求)”。 request结构体用来表征等待进行的I/O请求。驱动程序通过 CURRENT(一个指向 blk_devM

16、AJOR_NR. request_queue 的指针)访问请求结构中的成员,通过这些成员,驱动程序可以了解到在缓冲区缓存和物理块设备之间进行数据传输所需的所有信息。,15,request结构(在blkdev.h中定义),struct request struct list_head queue;int elevator_sequence;volatile int rq_status; /* should split this into a few status bits */ #define RQ_INACTIVE (-1) #define RQ_ACTIVE 1 #define RQ_SCS

17、I_BUSY 0xffff #define RQ_SCSI_DONE 0xfffe #define RQ_SCSI_DISCONNECTING 0xffe0kdev_t rq_dev;int cmd; /* READ or WRITE */int errors;unsigned long sector;unsigned long nr_sectors;unsigned long hard_sector, hard_nr_sectors;unsigned int nr_segments;unsigned int nr_hw_segments;unsigned long current_nr_se

18、ctors;void * special;char * buffer;struct completion * waiting;struct buffer_head * bh;struct buffer_head * bhtail;request_queue_t *q; ;,16,request结构,kdev_t rq_dev; 请求所访问的设备。 默认情况下,某个特定驱动程序所管理的所有设备会使用相同的 request 函数,rq_dev 可用来表示实际操作的次设备。 CURRENT_DEV 宏被简单定义为 DEVICE_NR(CURRENT-rq_dev) int cmd:要执行的操作, R

19、EAD(从设备中读取),或者 WRITE(向设备写入)。 unsigned long sector:本次请求要传输的第一个扇区号。,17,request结构,unsigned long current_nr_sectors:当前请求要传输的扇区数 char *buffer:数据要被写入或者要被读出的缓冲区。 struct buffer_head *bh:本次请求对应的缓冲区链表的第一个缓冲区,即缓冲区头,指向由buffer_head结构组成的一个链表。为满足I/O请求,需要在该链表的每个缓冲区上执行指定的 I/O 操作。,18,块设备驱动程序的设计,块设备驱动程序的组成: 文件包含与宏定义 b

20、lock_device_operations结构体变量 块设备驱动程序的接口函数 块设备驱动程序的request函数 块设备驱动的加载函数 块设备驱动的卸载函数,19,文件包含与宏定义,所有的块设备驱动程序都应包含,该文件定义了许多可由块驱动程序使用的常用代码,并提供了用来处理I/O请求队列的函数。 blk.h头文件在符号MAJOR_NR的基础上定义了若干符号,而MAJOR_NR必须在包含该头文件之前由驱动程序声明。,20,文件包含与宏定义,MAJOR_NR:用来访问 blk_dev 和 blksize_size等数组。 DEVICE_NAME:设备名称,用于打印错误信息。 DEVICE_NR

21、(kdev_t device) 用于从 kdev_t 设备编号中获得物理设备的序号,可以是 MINOR(device) 或者其它表达式, 代表磁盘号(不是分区号)。 还被用来声明 CURRENT_DEV,在 request 函数中用来确定拥有与某个数据传输请求相关联的次设备号。 DEVICE_REQUEST :用来指定驱动程序所使用的 request 函数名称。,21,文件包含与宏定义,blkdemo示例驱动程序的文件包含与宏定义: #define MAJOR_NR blkdemo_major static int blkdemo_major; #define DEVICE_NAME “blk

22、demo“ #define DEVICE_NR(device) MINOR(device) #define DEVICE_REQUEST blkdemo_request #include ,22,block_device_operations结构体变量,块设备驱动程序中需定义一个block_device_operations结构体变量。 blkdemo示例: struct block_device_operations blkdemo_bdops = open: blkdemo_open, release: blkdemo_release, ;,23,块设备驱动程序的接口函数,打开函数open

23、() 释放函数release() ioctl()函数 check_media_change()函数 revilidate()函数,24,打开函数open(),打开函数open()进行块设备的初始化 在一个真实硬件设备的块驱动中,open()和release()方法还应当设置驱动和硬件的状态,这些工作可能包括启停磁盘、加锁一个可移出设备和分配DMA缓冲等。 open()和release()函数不是必须的,简单块设备驱动可以不提供open()和release()函数。,25,打开函数open(),int blkdemo_open (struct inode *inode, struct file

24、*filp) int num; num = DEVICE_NR (inode-i_rdev); if (!blkdemo_devnum.use_cnt) check_disk_change(inode-i_rdev);if (!blkdemo_devnum.data) return -ENOMEM; blkdemo_devnum.use_cnt+; MOD_INC_USE_COUNT; return 0;,26,释放函数release(),release()函数的作用与open()相反,将使用计数减1以及在最后一次关闭操作时关闭设备。一般在umount时被调用。,int blkdemo_rel

25、ease (struct inode *inode, struct file *filp) int num;num = DEVICE_NR (inode-i_rdev); blkdemo_devnum.use_cnt-;MOD_DEC_USE_COUNT; return 0; ,27,ioctl()函数,和字符设备类似,也可以通过ioctl来操作块设备。 块设备驱动程序共享了大量常见的ioctl命令。2.4 内核提供了一个blk_ioctl函数(在 中声明)实现常见命令。 通常,需要由驱动程序实现的命令是BLKGETSIZE和HDIO_GETEO,其它命令可以传递给 blk_ioctl 处理。

26、 BLKGETSIZE :当前设备的大小,以扇区数表示。设备大小值通过arg参数复制到用户空间变量中。mkfs可利用该ioctl命令了解将要创建的文件系统大小。 HDIO_GETGEO :磁盘的几何参数,通过hd_geometry 结构将几何参数返回给用户。,28,ioctl()函数,int blkdemo_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) int err; long size; struct hd_geometry geo; switch(cmd) case

27、BLKGETSIZE: if (!arg) return -EINVAL; err = ! access_ok (VERIFY_WRITE, arg, sizeof(long); if (err) return -EFAULT; size = 1024 * blkdemo_blk_sizeMINOR(inode-i_rdev) / blkdemo_hardsect_sizeMINOR(inode-i_rdev); if (copy_to_user(long *) arg, ,29,check_media_change()函数,check_media_change检查自从上次访问以来,设备是否发

28、生过变化,用于支持可移动介质设备。 check_media_change()只有一个 kdev_t 型参数,用来标识设备。返回值为 1 表明介质变化,反之返回 0。不支持移动设备时可将 bdops-check_media_change 设为 NULL。,int blkdemo_check_change(kdev_t i_rdev) int minor = MINOR(i_rdev); blkdemo_dev *dev = blkdemo_devices + minor; if (dev-data) return 0; /* still valid */ return 1; /* expired

29、 */ ,30,revilidate()函数,revilidate是在磁盘变化之后,重新初始化驱动程序状态,也用于支持可移动介质设备。 revalidate() 函数在检测到磁盘变化时调用。返回 0 以表示成功,返回负的错误值以表示错误。 由 revalidation 执行的操作是设备特有的,但 通常用来更新内部的状态信息以便反映出新的设备。,int blkdemo_revalidate(kdev_t i_rdev) blkdemo_dev *dev = blkdemo_devices + MINOR(i_rdev); if (dev-data) return 0; dev-data = vm

30、alloc(dev-size); if (!dev-data) return -ENOMEM; return 0; ,31,request()函数,当需要读写设备时,读写请求就被放在请求队列中,系统自动调用request()函数来逐个处理请求队列中的请求。 request()函数原型为:void request_fn(request_queue_t *queue);,32,request()函数,测试请求是否有效是通过在blk.h中定义的宏INIT_REQUEST来完成,它检查系统的请求队列是否为空、请求的设备号是否与MAJOR_NR一致以及块设备是否上锁等。如果出现这些问题,则中断对请求的处

31、理。 数据传输是将当前请求缓冲区中的数据复制到设备(写)或从设备复制数据到当前请求缓冲区中(读),读写过程跟具体设备有关。数据传输过程使用CURRENT变量指向将要处理的请求即request 。,33,request()函数,void blkdemo_request(request_queue_t *q) struct request *req;int res=1; int num; int size; u8 *ptr;while(1) INIT_REQUEST; req = CURRENT;num = DEVICE_NR(req-rq_dev);ptr = blkdemo_devnum.da

32、ta + req-sector * blkdemo_devnum.hardsect;size = req-current_nr_sectors * blkdemo_devnum.hardsect; if (ptr + size blkdemo_devnum.data + blkdemo_devnum.size) printk(KERN_WARNING “blkdemo: request past end of devicen“); res = 0; ,34,request()函数,printk(“request %p: cmd %i sec %li (nr. %li)n“, req, req-

33、cmd, req-sector, req-current_nr_sectors); switch(req-cmd) case READ: memcpy(req-buffer, ptr, size); res = 1; case WRITE: memcpy(ptr, req-buffer, size); res = 1; default:res = 0; end_request(res); ,35,request()函数,如果驱动程序是中断驱动的,request函数应该提交一次数据传输,并立刻返回,而无需调用end_request 中断处理程序需要调用end_request函数来完成数据传输,在

34、调用end_request之前,不认为请求已经完成。 块数据的传输在内核调用驱动程序的request函数时开始,然后返回等待请求完成,直到中断发生。 设备在处理请求时,可以累积新的请求。这种情况下,可重入调用是允许的。 中断处理程序要执行几个操作 首先检查所有已发出传输的状态,并清除请求。 如果还有需要处理的请求,中断处理程序要启动下一次请求。,36,加载函数,37,硬件初始化,块设备需要与CPU接口,因此操作块设备前需要选择相关的CPU引脚、初始化用于连接块设备的相关接口。,38,注册块设备驱动,与字符设备的注册函数类似,如下所示: #ifdef CONFIG_DEVFS_FS static

35、 devfs_handle_t devfs_ts_dir, devfs_tsraw; #endif int ret; #ifdef CONFIG_DEVFS_FS ret=devfs_register_blkdev(MAJOR_NR, DEVICE_NAME, ,39,注册块设备驱动,#else ret=register_blkdev(MAJOR_NR,DEVICE_NAME,40,初始化请求队列,通过 blk_init_queue()函数初始化请求队列,以告知内核设备用来处理相关请求的请求函数。函数原型为:blk_init_queue(request_queue_t *queue, requ

36、est_fn_proc *request); 在模块的清除阶段,应调用blk_cleanup_queue(request_queue_t *q) 函数清除请求队列。 每个设备有一个默认请求队列, BLK_DEFAULT_QUEUE(major) 可得到该默认队列。 #define BLK_DEFAULT_QUEUE(_MAJOR) &blk_dev_MAJOR.request_queue,41,初始化全局数组,有一些全局数组保存了块设备驱动程序的信息,这些数组在 drivers/block/ll_rw_block.c 中声明和描述,如: int read_aheadMAX_BLKDEV; st

37、ruct blk_dev_struct blk_devMAX_BLKDEV; int *blk_sizeMAX_BLKDEV;int *blksize_sizeMAX_BLKDEV; int *hardsect_sizeMAX_BLKDEV; int *max_readaheadMAX_BLKDEV; int *max_sectorsMAX_BLKDEV; MAX_BLKDEV在中被定义成255,表示最多有255个主设备。,42,初始化全局数组,int *blk_sizeMAX_BLKDEV blk_sizemajorminor描述主设备号为major、次设备号为minor的分区大小,以KB为

38、单位。 通常将blk_sizemajor 指向一个一维数组。若blk_sizemajor 为 NULL,则不检查该设备的大小。int *blksize_sizeMAX_BLKDEV blksize_sizemajorminor描述主设备号为major、次设备号为minor的分区块大小,以字节为单位。 通常将若blksize_sizemajor 指向一个一维数组,若blksize_sizemajor为NULL,则假定块大小为 BLOCK_SIZE(当前定义为 1 KB)。,43,初始化全局数组,int * hardsect_sizeMAX_BLKDEV hardsect_sizemajormin

39、or描述主设备号为major、次设备号为minor的硬件扇区大小,= 512 B,默认 512 B。int *max_sectorsMAX_BLKDEV 单个请求的最大大小,通常设为硬件所能处理的最大传输大小。,44,初始化全局数组,int read_aheadMAX_BLKDEV int *max_readaheadMAX_BLKDEV 顺序读取一个文件时,内核预先读入的扇区数目。前者用于主设备号相同的所有分区,在块I/O级使用;后者用于单独分区,由主设备号和次设备号索引,在文件系统级使用,指的是文件中的块,磁盘上不一定是顺序的。 硬盘驱动程序通常将 read_ahead 设为 8 个扇区(

40、4 KB),max_readahead 默认 MAX_READAHEAD(当前为 31 页),可通过设备驱动程序的 iotcl 方法改变。,45,初始化全局数组,#define blkdemo_devs 2 /2 disks #define blkdemo_size 4 /4KBstatic int blkdemo_sizesblkdemo_devs; static int blkdemo_blksizesblkdemo_devs; static int blkdemo_hardsectsblkdemo_devs;read_aheadblkdemo_major = 2; for (i=0; i

41、 blkdemo_devs; i+)blkdemo_sizesi = blkdemo_size; /4K blk_sizeblkdemo_major=blkdemo_sizes; for (i=0; i blkdemo_devs; i+)blkdemo_blksizesi = 1024; blksize_sizeblkdemo_major=blkdemo_blksizes; for (i=0; i blkdemo_devs; i+)blkdemo_hardsectsi = 512; hardsect_sizeblkdemo_major=blkdemo_hardsects;,46,将gendis

42、k结构包含到全局链表,驱动程序应该将gendisk 结构包含到全局链表中,系统利用该链表实现了/proc/partitions。函数原型为:void add_gendisk(struct gendisk *gp);,static struct hd_struct blkdemo_part0; static struct gendisk blkdemo_gendisk = major: MAJOR_NR,major_name: DEVICE_NAME,minor_shift: 0,part: blkdemo_part,sizes: blkdemo_sizes, ;,add_gendisk(,47

43、,检测块设备是否准备好,检测块设备是否准备好,如果已准备好,则告知系统可对块设备进行操作。不同的块设备检测的过程不同。,48,初始化管理数据结构,驱动程序中需要设计一个专门用来管理设备的数据结构并对它进行初始化。 管理数据结构大体应包括设备大小、数据指针、使用次数、请求队列指针及gendisk指针等。 管理数据结构的初始化包括为管理数据结构分配内存空间及为某些成员分配内存空间。,49,初始化管理数据结构,struct blkdemo_device int size;int use_cnt;int hardsect;u8 *data;gendisk *gd; ;,50,初始化管理数据结构,#de

44、fine blkdemo_devs 2 /2 disks #define blkdemo_size 4 /4KBstatic int blkdemo_hardsectsblkdemo_devs; struct blkdemo_device blkdemo_devblkdemo_devs;for (i=0; i blkdemo_devs; i+) blkdemo_devi.size = 1024 * blkdemo_size; blkdemo_devi.hardsect = blkdemo_hardsectsi;blkdemo_devi.data = kmalloc(blkdemo_size *

45、1024 * sizeof (char), GFP_KERNEL);if(!blkdemo_devi.data)printk(“devices%d:cannot allocate memn“,i);return 1; ,51,解码块设备分区表,对于可分区设备,设备加载阶段还应调用register_disk()函数解码设备的分区表,原型如下: register_disk(struct gendisk *gd, int drive, unsigned minors,struct block_device_operations *ops, long size);,gd:事先准备好的 gendisk

46、结构 drive:设备号 minors:单个实际设备所支持的分区个数,如一块SD卡的分区数 ops:驱动程序的 block_device_operations 结构 size:设备以扇区计的大小 register_disk()函数执行后,将根据分区表中的信息在以宏DEVICE_NAME的值为主设备名的设备目录中建立分区文件。用户可以用这些分区文件名来操作磁盘的分区,如可以将某个分区mount到其它文件夹。,52,解码块设备分区表,for (i = 0; i blkdemo_devs; i+) register_disk(NULL, MKDEV(blkdemo_major, i), 1, ,53

47、,块设备驱动的卸载函数,54,删除分区文件,加载时在以宏DEVICE_NAME的值为目录名的主设备目录中建立了分区文件,卸载时用devfs_register_partitions()删除有关的分区文件。原型: void devfs_register_partitions (struct gendisk *dev, int minor,int unregister); dev:加载时使用的gendisk结构; minor:子设备的起始分区号,如每个子设备有4个分区,现对第2个子设备调用该函数,则此时minor为8; unregister为1,表示删除有关的分区文件。,55,注销块设备驱动,用来注

48、销块设备驱动程序的函数原型如下: int unregister_blkdev(unsigned int major, const char *name); 该函数使用的参数与注册时对应,major是驱动程序使用的主设备号,name是设备名称描述。,for (i=0; iblkdemo_devs; i+) fsync_dev(MKDEV(blkdemo_major, i); unregister_blkdev(blkdemo_major, DEVICE_NAME);,56,清除请求队列,在块设备的卸载过程中,应调用 blk_cleanup_queue 函数清除请求队列。原型: blk_clean

49、up_queue(request_queue_t *queue); 参数queue 与blk_init_queue()中的queue一致blkdemo驱动程序清除请求队列: blk_cleanup_queue(BLK_DEFAULT_QUEUE(blkdemo_major);,57,从全局链表删除gendisk 结构,加载时将gendisk结构加到了全局链表中,卸载时使用del_gendisk函数将该结构从全局链表中删除。原型:void del_gendisk(struct gendisk *gp); gp是加载时使用的gendisk结构,58,释放全局数组及管理数据结构相关内存空间,在块设备的卸载过程中,应释放为驱动程序分配的全局数组空间。 blkdemo示例:释放全局数组空间 read_aheadblkdemo_major = 0; blk_sizeblkdemo_major = NULL; blksize_sizeblkdemo_major = NULL; hardsect_sizeblkdemo_major = NULL; for (i=0; i blkdemo_devs; i+)if (blkdemo_devi.data) kfree(blkdemo_devi.data);,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报