add_partition,是添加磁盘分区信息的函数,负责向通用磁盘数据结构添加一个新的分区:
通用磁盘数据结构如下:
- struct gendisk {
- 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[32]; /* name of major driver */
- struct hd_struct **part; /* [indexed by minor] */
- struct block_device_operations *fops;
- struct request_queue *queue;
- void *private_data;
- sector_t capacity;
- int flags;
- struct device *driverfs_dev; // FIXME: remove
- struct device dev;
- struct kobject *holder_dir;
- struct kobject *slave_dir;
- struct timer_rand_state *random;
- int policy;
- atomic_t sync_io; /* RAID */
- unsigned long stamp;
- int in_flight;
- #ifdef CONFIG_SMP
- struct disk_stats *dkstats;
- #else
- struct disk_stats dkstats;
- #endif
- struct work_struct async_notify;
- };
- vents *ev;
- #ifdef CONFIG_BLK_DEV_INTEGRITY
- struct blk_integrity *integrity;
- #endif
- int node_id;
- };
蓝色的struct hd_struct类型 指向分区相关的数据区。看下下面的函数,
首先是分配一个hd_struct类型的变量作为分区的结构:disk的第part个分区指针指向该结构。
- p = kzalloc(sizeof(*p), GFP_KERNEL);
- disk->part[part-1] = p;
然后是将分区的起始扇区赋值为入参start,扇区个数赋值为入参len,partno赋值为入参part。SCSI磁盘的分区个数最多为15个。
- void add_partition(struct gendisk *disk, int part, sector_t start, sector_t len, int flags)
- {
- struct hd_struct *p;
- int err;
- p = kzalloc(sizeof(*p), GFP_KERNEL);
- if (!p)
- return;
- if (!init_part_stats(p)) {
- kfree(p);
- return;
- }
- p->start_sect = start;
- p->nr_sects = len;
- p->partno = part;
- p->policy = disk->policy;
- if (isdigit(disk->dev.bus_id[strlen(disk->dev.bus_id)-1]))
- snprintf(p->dev.bus_id, BUS_ID_SIZE,
- "%sp%d", disk->dev.bus_id, part);
- else
- snprintf(p->dev.bus_id, BUS_ID_SIZE,
- "%s%d", disk->dev.bus_id, part);
- device_initialize(&p->dev);
- p->dev.devt = MKDEV(disk->major, disk->first_minor + part);
- p->dev.class = &block_class;
- p->dev.type = &part_type;
- p->dev.parent = &disk->dev;
- disk->part[part-1] = p;
- /* delay uevent until 'holders' subdir is created */
- p->dev.uevent_suppress = 1;
- device_add(&p->dev);
- partition_sysfs_add_subdir(p);
- p->dev.uevent_suppress = 0;
- if (flags & ADDPART_FLAG_WHOLEDISK)
- err = device_create_file(&p->dev, &dev_attr_whole_disk);
- /* suppress uevent if the disk supresses it */
- if (!disk->dev.uevent_suppress)
- kobject_uevent(&p->dev.kobj, KOBJ_ADD);
- }
接下来的问题是,每个磁盘有几个分区,每个分区的起始扇区是多少,每个分区的扇区个数是多少是怎么来的。
我们先看下rescan_partitions 中添加分区的流程
- struct parsed_partitions *state;
...
- for (p = 1; p < state->limit; p++) {
- sector_t size = state->parts[p].size;
- sector_t from = state->parts[p].from;
- if (!size)
- continue;
- if (from + size > get_capacity(disk)) {
- printk(" %s: p%d exceeds device capacity\n",
- disk->disk_name, p);
- }
- add_partition(disk, p, from, size, state->parts[p].flags);
- #ifdef CONFIG_BLK_DEV_MD
- if (state->parts[p].flags & ADDPART_FLAG_RAID)
- md_autodetect_dev(bdev->bd_dev+p);
- #endif
- }
我们看到,add_partition的入参比如分区起始扇区,分区扇区的个数等都是从struct parsed_partitions *state 获取到的,那么state指向的数据是从哪里来的呢,这个问题浮出水面,就到了检测分区的函数check_partition闪亮登场的时刻了。
- static struct parsed_partitions *
- check_partition(struct gendisk *hd, struct block_device *bdev)
- {
- struct parsed_partitions *state;
- int i, res, err;
- state = kzalloc(sizeof(struct parsed_partitions), GFP_KERNEL);
- if (!state)
- return NULL;
- state->pp_buf = (char *)__get_free_page(GFP_KERNEL);
- if (!state->pp_buf) {
- kfree(state);
- return NULL;
- }
- state->pp_buf[0] = '\0';
- state->bdev = bdev;
- disk_name(hd, 0, state->name);
- snprintf(state->pp_buf, PAGE_SIZE, " %s:", state->name);
- if (isdigit(state->name[strlen(state->name)-1]))
- sprintf(state->name, "p");
- state->limit = disk_max_parts(hd);
- i = res = err = 0;
- while (!res && check_part[i]) {
- memset(&state->parts, 0, sizeof(state->parts));
- res = check_part[i++](state);
- if (res < 0) {
- /* We have hit an I/O error which we don't report now.
- * But record it, and let the others do their job.
- */
- err = res;
- res = 0;
- }
- }
- if (res > 0) {
- printk(KERN_INFO "%s", state->pp_buf);
- free_page((unsigned long)state->pp_buf);
- return state;
- }
- if (state->access_beyond_eod)
- err = -ENOSPC;
- if (err)
- /* The partition is unrecognized. So report I/O errors if there were any */
- res = err;
- if (!res)
- strlcat(state->pp_buf, " unknown partition table\n", PAGE_SIZE);
- else if (warn_no_part)
- strlcat(state->pp_buf, " unable to read partition table\n", PAGE_SIZE);
- printk(KERN_INFO "%s", state->pp_buf);
- free_page((unsigned long)state->pp_buf);
- kfree(state);
- return ERR_PTR(res);
- }
很吓人是不?原理起始很简单,最关键语句是绿色的那一句。我们知道有很多种类型的磁盘,不可能存在一个处理函数,能够解析出所有磁盘分区信息。那怎么办呢。神农 尝百草,一个一个的实验。我们有个函数数组 check_part,每种函数解析一种磁盘分区信息。那个函数能成功的解析磁盘分区信息,就不需要继续尝试了,可以打完收工了。当然解析出来的数据 存放在struct parsed_partitions 类型的结构体state中。
那么神农尝百草,那么一共有多少种草呢?
看下函数数组的定义:
- static int (*check_part[])(struct parsed_partitions *, struct block_device *) = {
- /*
- * Probe partition formats with tables at disk address 0
- * that also have an ADFS boot block at 0xdc0.
- */
- #ifdef CONFIG_ACORN_PARTITION_ICS
- adfspart_check_ICS,
- #endif
- #ifdef CONFIG_ACORN_PARTITION_POWERTEC
- adfspart_check_POWERTEC,
- #endif
- #ifdef CONFIG_ACORN_PARTITION_EESOX
- adfspart_check_EESOX,
- #endif
- /*
- * Now move on to formats that only have partition info at
- * disk address 0xdc0. Since these may also have stale
- * PC/BIOS partition tables, they need to come before
- * the msdos entry.
- */
- #ifdef CONFIG_ACORN_PARTITION_CUMANA
- adfspart_check_CUMANA,
- #endif
- #ifdef CONFIG_ACORN_PARTITION_ADFS
- adfspart_check_ADFS,
- #endif
- #ifdef CONFIG_EFI_PARTITION
- efi_partition, /* this must come before msdos */
- #endif
- #ifdef CONFIG_SGI_PARTITION
- sgi_partition,
- #endif
- #ifdef CONFIG_LDM_PARTITION
- ldm_partition, /* this must come before msdos */
- #endif
- #ifdef CONFIG_MSDOS_PARTITION
- msdos_partition,
- #endif
- #ifdef CONFIG_OSF_PARTITION
- osf_partition,
- #endif
- #ifdef CONFIG_SUN_PARTITION
- sun_partition,
- #endif
- #ifdef CONFIG_AMIGA_PARTITION
- amiga_partition,
- #endif
- #ifdef CONFIG_ATARI_PARTITION
- atari_partition,
- #endif
- #ifdef CONFIG_MAC_PARTITION
- mac_partition,
- #endif
- #ifdef CONFIG_ULTRIX_PARTITION
- ultrix_partition,
- #endif
- #ifdef CONFIG_IBM_PARTITION
- ibm_partition,
- #endif
- #ifdef CONFIG_KARMA_PARTITION
- karma_partition,
- #endif
- #ifdef CONFIG_SYSV68_PARTITION
- sysv68_partition,
- #endif
- NULL
- };
这么多函数,来自五湖四海,就是为了共同的目的,封装到了一起,就是为了获取分区的信息。把获取到信息存入结构体struct parsed_partitions *state;包含 分区的起始扇区,分区扇区的个数,flag等信息。
定义如下:
- struct parsed_partitions {
- char name[BDEVNAME_SIZE];
- struct {
- sector_t from;
- sector_t size;
- int flags;
- } parts[MAX_PART];
- int next;
- int limit;
- };
参考文献:
1 Linux 那些事儿
2 Linux Kernel Source Code