块设备驱动程序-内存盘
块设备驱动程序
为什么需要块设备驱动
块设备驱动程序是针对类似FLASH
这类的设备,这类设备的写操作一般基本单位不是一个字节,而是一个块,写一个字节就要先把这个扇区先读回来再写。
总的来说,就是合并同类型操作,优化操作。先不执行,放入队列,优化后再执行
小结
-
应用程序通过文件系统做种调用到
ll_rw_block
来实现块设备文件的读写,所谓块设备就是根据硬件的限制优化了读写的顺序,将读写先放入队列然后执行. -
这里有个统一的管理结构
gendisk
,包含了一个执行队列queue
-
ll_rw_block
最终会调用队列中的执行函数gendisk->queue->request_fn
也就是说读写函数实际是在这里实现的,其他都是框架做好了的
框架分析
ll_rw_block
我们从入口函数 ll_rw_block===low-level access to block devices
开始分析,该文件在fs\buffer.c
- buffer_head 包含了数据传输三要素源目的长度
- rw 读、写
- nr 多少个传输块,也就是多少个
buffer_head
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
//rw:读写标志位, nr:bhs[]长度, bhs[]:要读写的数据数组
{
int i;
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[i]; //获取nr个buffer_head
... ...
if (rw == WRITE || rw == SWRITE) {
if (test_clear_buffer_dirty(bh)) {
... ...
submit_bh(WRITE, bh); //提交WRITE写标志的buffer_head
continue;
}}
else {
if (!buffer_uptodate(bh)) {
... ...
submit_bh(rw, bh); //提交其它标志的buffer_head
continue;
}}
unlock_buffer(bh); }
}
struct buffer_head {
unsigned long b_state; //缓冲区状态标志
struct buffer_head *b_this_page; //页面中的缓冲区
struct page *b_page; //存储缓冲区位于哪个页面
sector_t b_blocknr; //逻辑块号
size_t b_size; //块的大小
char *b_data; //页面中的缓冲区
struct block_device *b_bdev; //块设备,来表示一个独立的磁盘设备
bh_end_io_t *b_end_io; //I/O完成方法
void *b_private; //完成方法数据
struct list_head b_assoc_buffers; //相关映射链表
/* mapping this buffer is associated with */
struct address_space *b_assoc_map;
atomic_t b_count; //缓冲区使用计数
};
submit_bh
接着分析提交传输块submit_bh
,通过bh来构造bio,然后调用submit_bio()提交bio
int submit_bh(int rw, struct buffer_head * bh)
{
struct bio *bio; //定义一个bio(block input output),也就是块设备i/o
... ...
bio = bio_alloc(GFP_NOIO, 1); //分配bio
/*根据buffer_head(bh)构造bio */
bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9); //存放逻辑块号
bio->bi_bdev = bh->b_bdev; //存放对应的块设备
bio->bi_io_vec[0].bv_page = bh->b_page; //存放缓冲区所在的物理页面
bio->bi_io_vec[0].bv_len = bh->b_size; //存放扇区的大小
bio->bi_io_vec[0].bv_offset = bh_offset(bh); //存放扇区中以字节为单位的偏移量
bio->bi_vcnt = 1; //计数值
bio->bi_idx = 0; //索引值
bio->bi_size = bh->b_size; //存放扇区的大小
bio->bi_end_io = end_bio_bh_io_sync; //设置i/o回调函数
bio->bi_private = bh; //指向哪个缓冲区
... ...
submit_bio(rw, bio); //提交bio
... ...
}
submit_bio
然后提交bio
void submit_bio(int rw, struct bio *bio)
{
...
generic_make_request(bio);
}
generic_make_request
可以看到如果current
链表为空,执行__generic_make_request
,否则加入到current
中
void generic_make_request(struct bio *bio)
{
if (current->bio_tail) { // current->bio_tail不为空,表示有bio正在提交
*(current->bio_tail) = bio; //将当前的bio放到之前的bio->bi_next里面
bio->bi_next = NULL; //更新bio->bi_next=0;
current->bio_tail = &bio->bi_next; //然后将当前的bio->bi_next放到current->bio_tail里,使下次的bio就会放到当前bio->bi_next里面了
return; }
BUG_ON(bio->bi_next);
do {
current->bio_list = bio->bi_next;
if (bio->bi_next == NULL)
current->bio_tail = ¤t->bio_list;
else
bio->bi_next = NULL;
__generic_make_request(bio); //调用__generic_make_request()提交bio
bio = current->bio_list;
} while (bio);
current->bio_tail = NULL; /* deactivate */
}
__generic_make_request
获取队列并提交队列和bio
static inline void __generic_make_request(struct bio *bio)
{
request_queue_t *q;
int ret;
... ...
do {
q = bdev_get_queue(bio->bi_bdev); //通过bio->bi_bdev获取申请队列q
... ...
ret = q->make_request_fn(q, bio); //提交申请队列q和bio
} while (ret);
}
make_request_fn
这个具体的队列函数是一个函数指针,在blk_queue_make_request
初始化
blk_init_queue_node()
{
blk_queue_make_request(q, __make_request);
}
void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn)
{
q->make_request_fn = mfn;
..
}
__make_request
- 执行电梯排序算法
elv_merge
,如果合并失败则单独将bio加入队列 __generic_unplug_device
是执行函数
static int __make_request(request_queue_t *q, struct bio *bio)
{
struct request *req; //块设备本身的队列
... ...
//(1)将之前的申请队列q和传入的bio,通过排序,合并在本身的req队列中
el_ret = elv_merge(q, &req, bio);
... ...
init_request_from_bio(req, bio); //合并失败,单独将bio放入req队列
add_request(q, req); //单独将之前的申请队列q放入req队列
... ...
__generic_unplug_device(q); //(2) 执行申请队列的处理函数
}
__generic_unplug_device.
这个涉及到了具体的例子,可以参考下例子drivers\block\xd.c
void __generic_unplug_device(request_queue_t *q)
{ if (unlikely(blk_queue_stopped(q)))
return;
if (!blk_remove_plug(q))
return;
q->request_fn(q);
}
实际的例子如下:
//请求队列成员
struct request_queue
{
...
request_fn_proc *request_fn;
}
//定义了这么一个结构体的队列
static struct request_queue *xd_queue;
static int __init xd_init(void)
{
//初始化这个队列
xd_queue = blk_init_queue(do_xd_request, &xd_lock);
}
//初始化队列的函数原型,也就是 do_xd_request 就是函数request_fn
// *rfn: request_fn_proc结构体,用来执行申请队列中的处理函数
// *lock:队列访问权限的自旋锁(spinlock),该锁需要通过DEFINE_SPINLOCK()函数来定义
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
return blk_init_queue_node(rfn, lock, -1);
}
request_queue_t *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{...
q->request_fn = rfn;
}
do_xd_request
也就是说实际的队列函数就是do_xd_request
,这里可以看到先使用elv_next_request
的电梯算法取出队列,然后执行读写xd_readwrite
//实际的队列执行函数如下do_xd_request
static void do_xd_request (request_queue_t * q)
{
struct request *req;
if (xdc_busy)
return;
while ((req = elv_next_request(q)) != NULL) //(1)while获取申请队列中的需要处理的申请
{
int res = 0;
... ...
for (retry = 0; (retry < XD_RETRIES) && !res; retry++)
res = xd_readwrite(rw, disk, req->buffer, block, count);
//将获取申请req的buffer成员 读写到disk扇区中,当读写失败返回0,成功返回1
end_request(req, res); //申请队列中的的申请已处理结束,当res=0,表示读写失败
}
}
自己写程序实现内存盘
这里实现一个内存盘,参考程序如下,这里具体的设备都是由gendisk
所控制
drivers/block/xd.c
drivers/block/z2ram.c
struct gendisk {
int major; //设备主设备号,等于register_blkdev()函数里的major
int first_minor; //起始次设备号,等于0,则表示此设备号从0开始的
int minors; //分区(次设备)数量,当使用alloc_disk()时,就会自动设置该成员
char disk_name[32]; //块设备名称, 等于register_blkdev()函数里的name
struct hd_struct **part; /*分区表的信息*/
int part_uevent_suppress;
struct block_device_operations *fops; //块设备操作函数
struct request_queue *queue; //请求队列,用于管理该设备IO请求队列的指针*
void *private_data; /*私有数据*/
sector_t capacity; /*扇区数,512字节为1个扇区,描述设备容量*/
....
};
-
分配
gendisk
结构ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
-
分配设置队列,队列提供实际的读写函数,这个会赋值到
gendisk
static DEFINE_SPINLOCK(ramblock_lock); ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); //具体的读写函数,需要取出队列 static void do_ramblock_request (request_queue_t * q) { struct request *req; static int cnt=0; printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) { .... end_request(req, 1); } }
-
注册块设备驱动,对比注册字符设备驱动,这么并没有提供
file operation
操作major = register_blkdev(0, "ramblock"); /* cat /proc/devices */
-
设置
gendisk
参数,包括先前的队列ramblock_disk->major = major; //主设备号 ramblock_disk->first_minor = 0; //扇区起始的编号 sprintf(ramblock_disk->disk_name, "ramblock"); ramblock_disk->fops = &ramblock_fops; //这个是必备的 set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); //内核认为扇区是512字节大小的 ramblock_disk->queue = ramblock_queue; static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, };
-
注册这个
gendisk
add_disk
1th队列获取执行
这里最关键的函数在队列的读取执行函数,需要取出队列,否则程序执行后卡死
static void do_ramblock_request (request_queue_t * q)
{
struct request *req;
static int cnt=0;
printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
....
end_request(req, 1);
}
}
完整的程序
/* 参考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
struct gendisk *ramblock_disk ;
static struct request_queue *ramblock_queue;
static DEFINE_SPINLOCK(ramblock_lock);
static int major;
#define RAMBLOCK_SIZE (1024*1024)
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
};
static void do_ramblock_request (request_queue_t * q)
{
struct request *req;
static int cnt=0;
printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
end_request(req, 1);
}
}
static int __init ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
//2. set queue
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
//3. set gendisk
major=register_blkdev(0, "ramblock");
ramblock_disk->major = major; //主设备号
ramblock_disk->first_minor = 0; //扇区起始的编号
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops; //这个是必备的
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); //内核认为扇区是512字节大小的
ramblock_disk->queue = ramblock_queue;
add_disk(ramblock_disk);
}
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
}
module_init(ramblock_init);
module_exit(ramblock_exit);
MODULE_LICENSE("GPL");
测试
# insmod ramblock.ko
ramblock:do_ramblock_request 1
unknown partition table
# 可以尝试读取文件来达到队列的执行
# hexdump /dev/ramblock
do_ramblock_request 2
# ls /dev/ramblock* -l
brw-rw---- 1 0 0 254, 0 Jan 1 00:26 /dev/ramblock
# cat /proc/devices
Block devices:
254 ramblock
2th 内存盘实现
-
分配一块内存给这个内存盘
kzalloc
//声明一个块内存 static unsigned char *ramblock_buf; ramblock_buf = kzalloc (RAMBLOCK_SIZE, GFP_KERNEL); //分配内存 //kfree(ramblock_buf);
-
提供相应的读写函数
static void do_ramblock_request (request_queue_t * q) { struct request *req; static int cnt=0; printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) { /* 数据传输三要素: 源,目的,长度 */ /* 源/目的: */ // which sector unsigned long offset = req->sector * 512; /* 目的/源: */ // req->buffer /* 长度: */ // 当前要传输多少个扇区 unsigned long len = req->current_nr_sectors * 512; if (rq_data_dir(req) == READ) { memcpy(req->buffer, ramblock_buf+offset, len); } else { memcpy(ramblock_buf+offset, req->buffer, len); } end_request(req, 1); } }
测试
-
安装工具
mkdosfs
-
下载
dosfstools_2.11.orig.tar.gz
-
创建临时目录
mkdir tmp
,mv dosfstools_2.11.orig.tar.gz tmp/
-
解压
cd tmp && tar zxvf dosfstools_2.11.orig.tar.gz
-
编译
cd dosfstools-2.11/ && make CC=arm-linux-gcc
-
复制,注意这里是应用程序复制
cp tmp/dosfstools-2.11/mkdosfs/mkdosfs /usr/sbin/
-
-
加载驱动,依然提示无分区,因为使用
kzalloc
是初始化0,确实没有分区表 -
搞个分区
# mkdosfs /dev/ramblock mkdosfs 2.11 (12 Mar 2005) do_ramblock_request 2 do_ramblock_request 3 unable to get drive geometry, usindo_ramblock_request 4 g default 255/63#
-
挂载
mount /dev/ramblock /tmp/
-
这个时候可以读写文件了
vi a.c
,写完保存后卸载umount /tmp
-
这个时候
tmp
下是空的了,再挂载a.c
恢复了 -
可以保存成一个文件,
cat /dev/ramblock > /mnt/ramblock.bin
-
在虚拟机中查看这个文件
# -o loop 回还设备:可将一个普通文件当成一个块设备文件挂接 sudo mount -o loop ramblock.bin /mnt
完整代码
/* 参考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
//声明一个块内存
static unsigned char *ramblock_buf;
struct gendisk *ramblock_disk ;
static struct request_queue *ramblock_queue;
static DEFINE_SPINLOCK(ramblock_lock);
static int major;
#define RAMBLOCK_SIZE (1024*1024)
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
};
static void do_ramblock_request (request_queue_t * q)
{
struct request *req;
static int cnt=0;
printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
// which sector
unsigned long offset = req->sector * 512;
/* 目的/源: */
// req->buffer
/* 长度: */
// 当前要传输多少个扇区
unsigned long len = req->current_nr_sectors * 512;
if (rq_data_dir(req) == READ)
{
memcpy(req->buffer, ramblock_buf+offset, len);
}
else
{
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1);
}
}
static int __init ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
//2. set queue
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock)
//3. set gendisk
major=register_blkdev(0, "ramblock");
ramblock_disk->major = major; //主设备号
ramblock_disk->first_minor = 0; //扇区起始的编号
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops; //这个是必备的
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); //内核认为扇区是512字节大小的
ramblock_disk->queue = ramblock_queue;
ramblock_buf = kzalloc (RAMBLOCK_SIZE, GFP_KERNEL); //分配内存
add_disk(ramblock_disk);
}
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
kfree(ramblock_buf);
}
module_init(ramblock_init);
module_exit(ramblock_exit);
MODULE_LICENSE("GPL");
3th查看电梯排序
我们可以在电梯算法获取队列中打印出来这个顺序
static void do_ramblock_request (request_queue_t * q)
{
struct request *req;
static int r_cnt=0,w_cnt=0;
//printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
// which sector
unsigned long offset = req->sector * 512;
/* 目的/源: */
// req->buffer
/* 长度: */
// 当前要传输多少个扇区
unsigned long len = req->current_nr_sectors * 512;
if (rq_data_dir(req) == READ)
{
printk("do_rambloc_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf+offset, len);
}
else
{
printk("do_rambloc_request read %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1);
}
}
测试
-
安装模块读取了一次
# insmod ramblock.ko ramblock:read 1 unknown partition table
-
格式化中的读写
# mkdosfs /dev/ramblock mkdosfs 2.11 (12 Mar 2005) read 2 read 3 unable to get drive geometry, using default 255/63write 1 write 2 write 3 write 4 write 5
-
挂载时读取了多次
# mount /dev/ramblock /tmp read 4 ... read 42
-
复制文件到内存盘,并没有立即写
# cp Makefile /tmp/ read 43
-
强制同步系统调用或者等一会
# 等一会 # write 6 write 7 write 8 write 9
-
删除文件也是一样的
# rm /tmp/Makefile # sync write 10 write 11 write 12
4th 添加磁盘信息
古老的硬盘中有以下信息,由gendisk->struct block_device_operations *fops
提供接口
容量 = 磁头 * 柱面 * 扇区 * 512 ( 容量 = heads * sectors * cylinders * 512 )
磁头:即有多少面。这里假设有 2 面。
柱面:有多少环,这里假设有 32 环。
扇区:一环里有多少个扇区,这个得通过“公式”计算出来。
也就是说我们需要设置gendisk
的fops
的getgeo
接口
int (*getgeo)(struct block_device *, struct hd_geometry *)
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512 */
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = RAMBLOCK_SIZE/2/32/512;
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
测试
这里使用fdisk
,输入m
获得信息
#fdisk /dev/ramblock
Command (m for help): m
Command (m for help): m
Command Action
a toggle a bootable flag
b edit bsd disklabel
c toggle the dos compatibility flag
d delete a partition
l list known partition types
n add a new partition
o create a new empty DOS partition table
p print the partition table
q quit without saving changes
s create a new empty Sun disklabel
t change a partition's system id
u change display/entry units
v verify the partition table
w write table to disk and exit
x extra functionality (experts only)
-
n
来创建分区,继续输入p
输入分区编号,这里支持4个分区Command (m for help): n Command action e extended p primary partition (1-4) p
-
输入编号
1
,来设置第一个分区,这里有显示1--32
就是上面设置的柱面
Partition number (1-4): 1 First cylinder (1-32, default 1):
-
设置这个柱面的柱面范围
Partition number (1-4): 1 First cylinder (1-32, default 1): 1 Last cylinder or +size or +sizeM or +sizeK (1-32, default 32): 5
-
设置完成退出到主菜单,输入
p
查看分区表p print the partition table
Command (m for help): p Disk /dev/ramblock: 1 MB, 1048576 bytes 2 heads, 32 sectors/track, 32 cylinders Units = cylinders of 64 * 512 = 32768 bytes Device Boot Start End Blocks Id System /dev/ramblock1 1 5 144 83 Linux
-
继续输入
n
设置分区表Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 2 First cylinder (6-32, default 6): 6 Last cylinder or +size or +sizeM or +sizeK (6-32, default 32): 32
-
查看完成的分区表
Command (m for help): p Disk /dev/ramblock: 1 MB, 1048576 bytes 2 heads, 32 sectors/track, 32 cylinders Units = cylinders of 64 * 512 = 32768 bytes Device Boot Start End Blocks Id System /dev/ramblock1 1 5 144 83 Linux /dev/ramblock2 6 32 864 83 Linux
-
输入
w
保存Command (m for help): w The partition table has been altered! write 1 ramblock:read 14 ramblock1 ramblock2
-
查看这个设备文件,创建了多个分区,0表示整个磁盘,1表示第一个分区
# ls /dev/ramblock* -l brw-rw---- 1 0 0 254, 0 Jan 1 15:47 /dev/ramblock brw-rw---- 1 0 0 254, 1 Jan 1 15:47 /dev/ramblock1 brw-rw---- 1 0 0 254, 2 Jan 1 15:47 /dev/ramblock2
-
可以分别挂载格式化了
# mkdosfs /dev/ramblock1 mkdosfs 2.11 (12 Mar 2005) read 15 read 16 write 2 write 3 write 4 write 5 write 6 # mkdosfs /dev/ramblock2 mkdosfs 2.11 (12 Mar 2005) read 17 read 18 write 7 write 8 write 9 write 10 write 11 # mkdosfs /dev/ramblock0 mkdosfs 2.11 (12 Mar 2005) /dev/ramblock0: No such file or directory # mkdosfs /dev/ramblock mkdosfs 2.11 (12 Mar 2005) read 19 read 20 write 12 write 13 write 14 write 15 write 16
-
修改磁盘属性,默认是
linux
,使用指令t
,具体的格式L查看Command (m for help): t Selected partition 1 Hex code (type L to list codes): L 0 Empty 1b Hidden Win95 FAT32 9f BSD/OS 1 FAT12 1c Hidden W95 FAT32 (LBA) a0 Thinkpad hibernation 4 FAT16 <32M 1e Hidden W95 FAT16 (LBA) a5 FreeBSD 5 Extended 3c Part.Magic recovery a6 OpenBSD 6 FAT16 41 PPC PReP Boot a8 Darwin UFS 7 HPFS/NTFS 42 SFS a9 NetBSD a OS/2 Boot Manager 63 GNU HURD or SysV ab Darwin boot b Win95 FAT32 80 Old Minix b7 BSDI fs c Win95 FAT32 (LBA) 81 Minix / old Linux b8 BSDI swap e Win95 FAT16 (LBA) 82 Linux swap be Solaris boot f Win95 Ext'd (LBA) 83 Linux eb BeOS fs 11 Hidden FAT12 84 OS/2 hidden C: drive ee EFI GPT 12 Compaq diagnostics 85 Linux extended ef EFI (FAT-12/16/32) 14 Hidden FAT16 <32M 86 NTFS volume set f0 Linux/PA-RISC boot 16 Hidden FAT16 87 NTFS volume set f2 DOS secondary 17 Hidden HPFS/NTFS 8e Linux LVM fd Linux raid autodetect Hex code (type L to list codes): c Changed system type of partition 1 to c (Win95 FAT32 (LBA)) Command (m for help): p Disk /dev/ramblock: 1 MB, 1048576 bytes 2 heads, 32 sectors/track, 32 cylinders Units = cylinders of 64 * 512 = 32768 bytes Device Boot Start End Blocks Id System /dev/ramblock1 1 32 1008 c Win95 FAT32 (LBA)