Linux设备模型初始化——SCSI上层sd驱动分析

static int __init init_sd(void)
{
	int majors = 0, i;
	/*
	Linux中所有的主设备号也是被各种各样的设备所瓜分.其中,8,65-71,136-143这么个16个号码就被scsi disk所霸占了.
	sd_major()函数的返回值就是这16个数字.每个主设备号可以带256个次设备号.
	*/
	for (i = 0; i < SD_MAJORS; i++)
		// 注册块设备驱动程序。为块设备预订一个主设备号。
		if (register_blkdev(sd_major(i), "sd") == 0)
			majors++;

	return scsi_register_driver(&sd_template.gendrv);
}

init_sd主要内容如下:
(1)给sd驱动分配8,65-71,136-143这么个16个主设备号,当sd驱动探测到设备时就会从这16个主设备号里分配。
(2)调用scsi_register_driver注册sd驱动。

scsi_register_driver(&sd_template.gendrv);
	// scsi_bus_type目录在/sys/bus/scsi
	drv->bus = &scsi_bus_type;
	return driver_register(drv);
		INIT_LIST_HEAD(&drv->devices);
		init_MUTEX_LOCKED(&drv->unload_sem);
		return bus_add_driver(drv);
			// scsi_bus_type
			struct bus_type * bus = get_bus(drv->bus);
			// sd
			kobject_set_name(&drv->kobj, "%s", drv->name);
			// 所以驱动应该注册在scsi_bus_type的驱动目录下
			drv->kobj.kset = &bus->drivers;
			kobject_register(&drv->kobj)
			driver_attach(drv);
				// device_add 里会把设备加入到总线链表里
				list_for_each(entry, &bus->devices.list)
					struct device * dev = container_of(entry, struct device, bus_list);
					if (!dev->driver)
						driver_probe_device(drv, dev);
							dev->driver = drv;
							if (drv->probe)
								// 对于sd驱动就是 sd_probe
								int error = drv->probe(dev);
								if (error)
									dev->driver = NULL;
									return error;
							device_bind_driver(dev);
								list_add_tail(&dev->driver_list, &dev->driver->devices);
								/*
									/sys/bus/scsi/drivers/sd目录下创建0:0:0:0链接文件
									链接到/sys/devices/pci0000:00/0000:00:10.0/host0/target0:0:0/0:0:0:0
								*/
								sysfs_create_link(&dev->driver->kobj, &dev->kobj, kobject_name(&dev->kobj));
								/*
									/sys/devices/pci0000:00/0000:00:10.0/host0/target0:0:0/0:0:0:0目录下创建driver链接文件
									链接到/sys/bus/scsi/drivers/sd
								*/
								sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
			module_add_driver(drv->owner, drv);
			driver_add_attrs(bus, drv);

(1)将sd驱动注册到/sys/bus/scsi目录下。
(2)遍历总线的设备链表,用sd驱动的probe探测设备,如果探测到设备,就调用device_bind_driver把设备和驱动绑定起来。

sd驱动的probe函数就是sd_probe

static int sd_probe(struct device *dev)
{
	struct scsi_device *sdp = to_scsi_device(dev);
	struct scsi_disk *sdkp;
	struct gendisk *gd;

	/* 如果SCSI设备不是磁盘或者磁光盘,则退出 */
	if ((sdp->type != TYPE_DISK) && (sdp->type != TYPE_MOD))
		goto out;

	/* 分配一个scsi_disk描述符 */
	sdkp = kmalloc(sizeof(*sdkp), GFP_KERNEL);
	memset (sdkp, 0, sizeof(*sdkp));
	kref_init(&sdkp->kref);

	/* 分配通用磁盘描述符,SCSI磁盘最多16个分区 */
	gd = alloc_disk(16);

	/* 分配磁盘名,如sda-sdz,sdaa-sdzz */
	if (!idr_pre_get(&sd_index_idr, GFP_KERNEL))
		goto out_put;
	spin_lock(&sd_index_lock);
	error = idr_get_new(&sd_index_idr, NULL, &index);
	spin_unlock(&sd_index_lock);

	/* 设备数量太多,退出 */
	if (index >= SD_MAX_DISKS)
		error = -EBUSY;
	if (error)
		goto out_put;

	sdkp->device = sdp;
	sdkp->driver = &sd_template;
	sdkp->disk = gd;
	sdkp->index = index;
	sdkp->openers = 0;

	/* 设置超时时间 */
	if (!sdp->timeout) {
		if (sdp->type == TYPE_DISK)
			sdp->timeout = SD_TIMEOUT;
		else/* 磁光盘超时时间应当长一点 */
			sdp->timeout = SD_MOD_TIMEOUT;
	}

	/* 计算设备的主次设备号,特殊的计算方法是为了与2.4兼容 */
	gd->major = sd_major((index & 0xf0) >> 4);
	gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
	gd->minors = 16;
	/* 设置磁盘回调 */
	gd->fops = &sd_fops;

	if (index < 26) {
		sprintf(gd->disk_name, "sd%c", 'a' + index % 26);
	} else if (index < (26 + 1) * 26) {
		sprintf(gd->disk_name, "sd%c%c",
			'a' + index / 26 - 1,'a' + index % 26);
	} else {
		const unsigned int m1 = (index / 26 - 1) / 26 - 1;
		const unsigned int m2 = (index / 26 - 1) % 26;
		const unsigned int m3 =  index % 26;
		sprintf(gd->disk_name, "sd%c%c%c",
			'a' + m1, 'a' + m2, 'a' + m3);
	}

	strcpy(gd->devfs_name, sdp->devfs_name);

	/* 将驱动、设备、通用磁盘数据结构关联起来 */
	gd->private_data = &sdkp->driver;

	/* 发送SCSI命令获取设备信息并设置描述符字段 */
	sd_revalidate_disk(gd);

	gd->driverfs_dev = &sdp->sdev_gendev;
	gd->flags = GENHD_FL_DRIVERFS;
	if (sdp->removable)
		gd->flags |= GENHD_FL_REMOVABLE;
	gd->queue = sdkp->device->request_queue;

	dev_set_drvdata(dev, sdkp);
	/* 将该设备添加到通用块IO系统中 */
	add_disk(gd);

(1)分配scsi_disk和gendisk描述符。
(2)从idr_tree中查找一个空闲节点,并且获取该空闲节点的id号,并根据该id号生成盘符和设备的主次设备号。
(3)设备gendisk的请求队列为主机适配器驱动里为scsi_device分配的队列。
(4)调用add_disk将gendisk添加到通用块IO系统中。

void add_disk(struct gendisk *disk)
{
	/**
	 * 设置GENHD_FL_UP标志。
	 */
	disk->flags |= GENHD_FL_UP;
	/**
	 * 建立设备驱动程序和设备的主设备号之间的连接。
	 */
	blk_register_region(MKDEV(disk->major, disk->first_minor),
			    disk->minors, NULL, exact_match, exact_lock, disk);
	/**
	 * 注册设备驱动程序模型的gendisk的kobject结构,它作为设备驱动程序的一个新设备。
	 * 并扫描磁盘中的分区表,对每个分区,初始化其hd_struct描述符。同时注册设备驱动程序模型中的分区。
	 */
	register_disk(disk);
	/**
	 * 注册请求队列描述符中内嵌的kobject结构。
	 */
	blk_register_queue(disk);
}

比较重要的是register_disk函数

void register_disk(struct gendisk *disk)
{
	/* 设置磁盘名称 */
	strlcpy(disk->kobj.name,disk->disk_name,KOBJ_NAME_LEN);
	/* ewww... some of these buggers have / in name... */
	s = strchr(disk->kobj.name, '/');
	if (s)
		*s = '!';
	/* 将磁盘设备添加到sys文件系统中 */
	if ((err = kobject_add(&disk->kobj)))
		return;
	disk_sysfs_symlinks(disk);
		struct device *target = get_device(disk->driverfs_dev);
		/*
			/sys/block/sda目录下创建“device”链接文件,链接到
			/sys/devices/pci0000:00/0000:00:10.0/host0/target0:0:0/0:0:0:0
		*/
		sysfs_create_link(&disk->kobj,&target->kobj,"device");
		/*
			/sys/devices/pci0000:00/0000:00:10.0/host0/target0:0:0/0:0:0:0目录下创建“block”链接文件,
			链接到/sys/block/sda				
		*/
		sysfs_create_link(&target->kobj,&disk->kobj,"block");
	/* No minors to use for partitions */
	if (disk->minors == 1) {/* 没有逻辑分区,将设备添加到dev中 */
		if (disk->devfs_name[0] != '\0')
			devfs_add_disk(disk);
		return;
	}

	/* always add handle for the whole disk */
	/* 处理磁盘分区 */
	devfs_add_partitioned(disk);

	/* No such device (e.g., media were just removed) */
	/* 设备已经被移除,退出 */
	if (!get_capacity(disk))
		return;

	/* 获取设备的引用计数 */
	bdev = bdget_disk(disk, 0);
	if (!bdev)
		return;

	/* 读取磁盘分区标志 */
	bdev->bd_invalidated = 1;
	/* 扫描磁盘分区,建立磁盘与分区的关系,并将分区添加到系统中 */
	if (blkdev_get(bdev, FMODE_READ, 0) < 0)
		return;
	blkdev_put(bdev);
}

(1)调用kobject_add将通用磁盘设备gendisk加入到/sys/block中。
(2)调用disk_sysfs_symlinks将磁盘设备目录/sys/block/sda和pci设备目录/sys/devices/pci0000:00/0000:00:10.0/host0/target0:0:0/0:0:0:0关联起来。
(3)调用blkdev_get扫描磁盘分区,建立磁盘与分区的关系,并将分区添加到系统中。

我们看一下bdget_disk如何根据gendisk获得block_device的

static inline struct block_device *bdget_disk(struct gendisk *disk, int index)
	return bdget(MKDEV(disk->major, disk->first_minor) + index);
		/* 在dev文件系统中找到设备的inode */
		inode = iget5_locked(bd_mnt->mnt_sb, hash(dev), bdev_test, bdev_set, &dev);	
			struct hlist_head *head = inode_hashtable + hash(sb, hashval);
			inode = ifind(sb, head, test, data);
				inode = find_inode(sb, head, test, data);
				if (inode)
					__iget(inode);
			if (inode)
				return inode;
			// 如果没找到就分配一个inode
			return get_new_inode(sb, head, test, set, data);
				// alloc_inode 不仅仅分配了inode,还分配了device
				struct inode *inode = alloc_inode(sb);
					// 会调用bdev_alloc_inode
					if (sb->s_op->alloc_inode)
						inode = sb->s_op->alloc_inode(sb);
							struct bdev_inode *ei = kmem_cache_alloc(bdev_cachep, SLAB_KERNEL);
							return &ei->vfs_inode;
					else
						inode = (struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL);
				
				old = find_inode(sb, head, test, data);
				if (!old)
					set(inode, data)
						BDEV_I(inode)->bdev.bd_dev = *(dev_t *)data;
					inodes_stat.nr_inodes++;
					list_add(&inode->i_list, &inode_in_use);
					list_add(&inode->i_sb_list, &sb->s_inodes);
					hlist_add_head(&inode->i_hash, head);
					inode->i_state = I_LOCK|I_NEW;
					return inode;
		bdev = &BDEV_I(inode)->bdev;
		if (inode->i_state & I_NEW) {
			bdev->bd_contains = NULL;
			bdev->bd_inode = inode;
			bdev->bd_block_size = (1 << inode->i_blkbits);
			bdev->bd_part_count = 0;
			bdev->bd_invalidated = 0;
			inode->i_mode = S_IFBLK;
			inode->i_rdev = dev;
			inode->i_bdev = bdev;
			inode->i_data.a_ops = &def_blk_aops;
			mapping_set_gfp_mask(&inode->i_data, GFP_USER);
			inode->i_data.backing_dev_info = &default_backing_dev_info;
			spin_lock(&bdev_lock);
			list_add(&bdev->bd_list, &all_bdevs);
			spin_unlock(&bdev_lock);
			unlock_new_inode(inode);
		}
		return bdev;

(1)调用iget5_locked获得块设备的inode,如果没有就会创建,对于bdev会创建bdev_inode,这个结构体含有inode和block_device两个字段。
(2)初始化inode和block_device的各个字段。

下面看一下blkdev_get函数


blkdev_get(bdev, FMODE_READ, 0)
	struct file fake_file = {};
	struct dentry fake_dentry = {};
	fake_file.f_mode = mode;
	fake_file.f_flags = flags;
	fake_file.f_dentry = &fake_dentry;
	fake_dentry.d_inode = bdev->bd_inode;

	return do_open(bdev, &fake_file);
		disk = get_gendisk(bdev->bd_dev, &part);
			struct kobject *kobj = kobj_lookup(bdev_map, dev, part);
			return  kobj ? to_disk(kobj) : NULL;
		if (!bdev->bd_openers)
			/**
			 * 第一次访问,以前没有打开过
			 * 就初始化它的bd_disk
			 */
			bdev->bd_disk = disk;
			bdev->bd_contains = bdev;
			if (!part) {/* 是一个整盘,而不是分区 */
				struct backing_dev_info *bdi;
				if (disk->fops->open) {
					/**
					 * 该盘定义了打开方法。就执行它
					 * 该方法是由块设备驱动程序定义的定制函数。
					 */
					ret = disk->fops->open(bdev->bd_inode, file);
						sd_open(struct inode *inode, struct file *filp)
							sdkp = scsi_disk_get(disk)
								sdkp = scsi_disk(disk);
								return sdkp;
							sdev = sdkp->device;
					if (ret)
						goto out_first;
				}
				if (!bdev->bd_openers) {
					bd_set_size(bdev,(loff_t)get_capacity(disk)<<9);
					bdi = blk_get_backing_dev_info(bdev);
					if (bdi == NULL)
						bdi = &default_backing_dev_info;
					bdev->bd_inode->i_data.backing_dev_info = bdi;
				}
				if (bdev->bd_invalidated)
					rescan_partitions(disk, bdev);
						bdev->bd_invalidated = 0;
						/* 删除内存中的分区信息 */
						for (p = 1; p < disk->minors; p++)
							delete_partition(disk, p);
						state = check_partition(disk, bdev)
							state = kmalloc(sizeof(struct parsed_partitions), GFP_KERNEL);
							disk_name(hd, 0, state->name);
						/* 将check_partition检测到的分区添加到系统中 */
						for (p = 1; p < state->limit; p++) {
							sector_t size = state->parts[p].size;
							sector_t from = state->parts[p].from;
							if (!size)/* 分区长度为0,忽略 */
								continue;
							/* 将分区添加到系统中 */
							add_partition(disk, p, from, size);	
								struct hd_struct *p = kmalloc(sizeof(*p), GFP_KERNEL);
								p->start_sect = start;
								p->nr_sects = len;
								p->partno = part;									

								if (isdigit(disk->kobj.name[strlen(disk->kobj.name)-1]))
									snprintf(p->kobj.name,KOBJ_NAME_LEN,"%sp%d",disk->kobj.name,part);
								else
									snprintf(p->kobj.name,KOBJ_NAME_LEN,"%s%d",disk->kobj.name,part); // 比如sda1
								p->kobj.parent = &disk->kobj;
								p->kobj.ktype = &ktype_part;
								kobject_init(&p->kobj);
								// 注册 /dev/block/sda/sda1
								kobject_add(&p->kobj);
								// 向udev发生事件
								if (!disk->part_uevent_suppress)
									kobject_uevent(&p->kobj, KOBJ_ADD);
								sysfs_create_link(&p->kobj, &block_subsys.kset.kobj, "subsystem");
								partition_sysfs_add_subdir(p);								
								disk->part[part-1] = p;
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值