Linux SCSI设备容量打印代码分析

本文介绍SCSI设备容量获取的方法及代码实现,重点分析了sd_read_capacity函数如何读取设备容量,并探讨了容量打印、WriteProtect标志获取等扩展内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

探寻SCSI设备容量如何获取代码。

分析

8G USB转SD卡启动打印信息:

sd 3:0:0:0: [sdb] 15523840 512-byte logical blocks: (7.94 GB/7.40 GiB)

 获取设备容量,单位是sector,后面 的7.94 GB是容量字节除1000单位,7.40 GiB是容量字节除1024单位.

		if (sdkp->first_scan || old_capacity != sdkp->capacity) {
			sd_printk(KERN_NOTICE, sdkp,
				  "%llu %d-byte logical blocks: (%s/%s)\n",
				  (unsigned long long)sdkp->capacity,
				  sector_size, cap_str_10, cap_str_2);

 代码分析:

drivers/scsi/sd.c
	module_init(init_sd);
		scsi_register_driver(&sd_template.gendrv);
			static struct scsi_driver sd_template = {
				.owner			= THIS_MODULE,
				.gendrv = {
					.name		= "sd",
					.probe		= sd_probe,
			
sd_probe	
	async_schedule_domain(sd_probe_async, sdkp, &scsi_sd_probe_domain);
	
sd_probe_async	
	sd_revalidate_disk(gd);
		sd_read_capacity(sdkp, buffer);

重点代函数sd_read_capacity,

/*
 * read disk capacity
 */
static void
sd_read_capacity(struct scsi_disk *sdkp, unsigned char *buffer)
{
		sector_size = read_capacity_10(sdkp, sdp, buffer); 由于容量没有超过2T所以使用READ/WRITE_10

scsi_execute_req函数实现最终发送READ_CAPACITY命令下去,这里获取设备8字节 信息。最后解析除sectors_size和capacity个数。

#define READ_CAPACITY         0x25
#define READ_10               0x28
#define WRITE_10              0x2a

static int read_capacity_10(struct scsi_disk *sdkp, struct scsi_device *sdp,
						unsigned char *buffer)
{
	do {
		cmd[0] = READ_CAPACITY;
		memset(&cmd[1], 0, 9);
		memset(buffer, 0, 8);

		the_result = scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE,
					buffer, 8, &sshdr,
					SD_TIMEOUT, SD_MAX_RETRIES, NULL);
	} while (the_result && retries);

	sector_size = get_unaligned_be32(&buffer[4]);
	lba = get_unaligned_be32(&buffer[0]);

	sdkp->capacity = lba + 1;
	sdkp->physical_block_size = sector_size;
	return sector_size;
}

获取到容量信息和打印出来,就是文章开头的打印信息和打印代码。

扩展

1、Write Protect 标志:sd 3:0:0:0: [sdb] Write Protect is off

static void
sd_read_write_protect_flag(struct scsi_disk *sdkp, unsigned char *buffer)
{
	if (sdp->use_192_bytes_for_3f) {
		res = sd_do_mode_sense(sdp, 0, 0x3F, buffer, 192, &data, NULL)

	} else {
		sdkp->write_prot = ((data.device_specific & 0x80) != 0);
		set_disk_ro(sdkp->disk, sdkp->write_prot);
		if (sdkp->first_scan || old_wp != sdkp->write_prot) {
			sd_printk(KERN_NOTICE, sdkp, "Write Protect is %s\n",
				  sdkp->write_prot ? "on" : "off");
			sd_printk(KERN_DEBUG, sdkp,
				  "Mode Sense: %02x %02x %02x %02x\n",
				  buffer[0], buffer[1], buffer[2], buffer[3]);
		}

通过MODE_SENSE命令获取数据然后解析除Write Protect标志。

2、分区中的start和end是怎么获取出来的?

   Device Boot      Start         End      Blocks  Id System
/dev/sdb1   *        2048    15523839     7760896   b Win95 FAT32

设备容量是固定的,是几个分区容量的总和,但是start和end是我们在新建分区时确定下来的参数,fdisk有这个参数吗?

为何fdisk新建的分区都会预留空间,而Disk软件分区一般默认都不会预留空间?

怎么在代码中手动预留除空间不让其访问到边界。

 

 

 

 

 

<think>我们正在讨论如何编写Linux内核的真实磁盘驱动程序。根据引用[1]和[2],Linux驱动开发涉及内核架构、驱动程序工作原理以及各类驱动编写技巧。真实磁盘驱动通常属于块设备驱动,需要处理磁盘I/O请求。步骤分解: 1.理解Linux设备驱动框架:块设备驱动需要注册块设备,提供请求处理函数(request_fn)来处理I/O请求队列。2.初始化驱动:在模块初始化函数中,分配一个块设备结构(如struct gendisk),设置其容量、操作函数集(structblock_device_operations)等。 3.实现操作函数集:包括open、release、ioctl等基本操作,但最重要的是实现请求处理函数(request_fn)或使用现代的多队列机制(如blk-mq)。 4.处理I/O请求:在请求处理函数中,驱动需要从请求队列中获取请求(structrequest),将其转换为对物理设备的操作(如读写特定扇区)。 5.中断处理:当I/O操作完成时,设备通常会发出中断,驱动需要在中断处理函数中完成请求(调用blk_mq_end_request等)并启动下一个请求。 6.错误处理:处理可能出现的I/O错误,向上层报告。示例代码框架(基于传统请求队列): ```c#include <linux/blkdev.h>#include <linux/module.h> structmydisk_dev{struct gendisk*gd;struct request_queue *queue;//其他设备特定数据}; staticvoid mydisk_request(structrequest_queue*q){struct request*req;struct mydisk_dev*dev= q->queuedata;while((req= blk_peek_request(q)) !=NULL){//处理请求:根据req->cmd_type判断读写,req->sector起始扇区,req->current_nr_sectors扇区数//数据缓冲区:bio_data(req->bio)等//完成请求:blk_end_request(req,0,blk_rq_bytes(req));} }static intmydisk_open(structblock_device*bdev,fmode_t mode) {//打开设备return0; }static voidmydisk_release(struct gendisk*gd, fmode_tmode){//释放设备}static structblock_device_operationsmydisk_fops= {.owner =THIS_MODULE,.open =mydisk_open,.release =mydisk_release, };static int__initmydisk_init(void) {structmydisk_dev *dev;dev= kzalloc(sizeof(structmydisk_dev),GFP_KERNEL);//初始化请求队列dev->queue= blk_init_queue(mydisk_request,NULL);blk_queue_logical_block_size(dev->queue,512);dev->queue->queuedata =dev;//分配gendiskdev->gd= alloc_disk(1);dev->gd->major= register_blkdev(0,"mydisk");dev->gd->first_minor =0;dev->gd->fops= &mydisk_fops;dev->gd->queue= dev->queue;dev->gd->private_data= dev;snprintf(dev->gd->disk_name,DISK_NAME_LEN,"mydisk");set_capacity(dev->gd,1024*1024);//假设容量为512MB(1024*1024个扇区,每扇区512字节)add_disk(dev->gd);return0;} staticvoid __exit mydisk_exit(void) {structmydisk_dev *dev =...;//获取设备结构del_gendisk(dev->gd);put_disk(dev->gd);unregister_blkdev(dev->gd->major, "mydisk");blk_cleanup_queue(dev->queue);kfree(dev);} module_init(mydisk_init);module_exit(mydisk_exit); ```注意:现代内核推荐使用blk-mq(多队列)框架,以提高性能,特别是在多核系统上。但上述传统框架有助于理解基本概念。引用[5]提到启动过程中涉及从存储设备加载引导程序,这通常由底层设备驱动(如SD卡、NAND驱动)完成。真实磁盘驱动开发也需要考虑硬件初始化,如引用[5]中描述的复位、时钟和DDR初始化,但具体到磁盘驱动,硬件初始化可能包括设置磁盘控制器寄存器、DMA通道等。开发过程中,调试是关键。可以使用printk进行内核打印,或使用动态调试(如dyndbg)以及内核调试工具(如ftrace)[^2]。另外,引用[4]提醒我们学习Linux不要走弯路,应专注于当前目标。因此,我们应集中精力于块设备驱动开发,参考内核文档(如Documentation/block/)和现有驱动源码(如drivers/block/)。最后,真实磁盘驱动通常需要处理硬件中断。在初始化时,需要注册中断处理函数,并在其中处理I/O完成事件。例如:```cstaticirqreturn_tmydisk_interrupt(int irq,void *dev_id) {//检查中断状态,确认I/O完成//完成请求:blk_mq_complete_request(req) [对于blk-mq]或 blk_end_request()[传统]returnIRQ_HANDLED;} ```在模块初始化时申请中断号: ```crequest_irq(irq,mydisk_interrupt, IRQF_SHARED, "mydisk",dev);``` 综上所述,开发真实磁盘驱动涉及多个方面:块设备框架、请求处理、中断处理以及硬件初始化。建议参考内核中简单的块设备驱动(如ramdisk驱动drivers/block/brd.c)作为起点。 §§相关问题§§1. Linux设备驱动中,传统请求队列和blk-mq有什么区别? 2.如何调试Linux内核中的块设备驱动程序? 3.在块设备驱动中如何处理DMA传输?</think>### Linux真实磁盘驱动程序开发指南 编写Linux内核的真实磁盘驱动程序涉及与硬件交互、管理I/O请求和实现块设备接口。以下是关键开发步骤和技术要点: #### 1. **驱动开发基础** - **内核模块框架**:驱动程序以可加载内核模块形式实现 ```c #include <linux/module.h> #include <linux/blkdev.h> static int __init disk_drv_init(void) { /* 初始化代码 */ return 0; } static void __exit disk_drv_exit(void) { /* 清理代码 */ } module_init(disk_drv_init); module_exit(disk_drv_exit); MODULE_LICENSE("GPL"); ``` - **块设备注册**: ```c struct gendisk *disk = alloc_disk(1); // 创建gendisk结构 disk->major = register_blkdev(0, "mydisk"); // 动态分配主设备号 disk->fops = &my_block_ops; // 设置操作函数集 set_capacity(disk, sector_count); // 设置磁盘容量(扇区数) add_disk(disk); // 激活磁盘 ``` #### 2. **关键数据结构** - **`struct block_device_operations`**:实现核心操作函数 ```c static const struct block_device_operations my_block_ops = { .owner = THIS_MODULE, .open = my_open, .release = my_release, .ioctl = my_ioctl, .getgeo = my_getgeo, }; ``` - **I/O请求处理**:使用`request_fn`或现代`blk-mq`框架 ```c static void my_request(struct request_queue *q) { struct request *req; while ((req = blk_peek_request(q)) != NULL) { // 解析请求:req->cmd_type, bio_data(req->bio) // 执行物理设备读写操作 blk_end_request_all(req, 0); // 完成请求 } } ``` #### 3. **硬件交互层** - **DMA数据传输**: ```c dma_addr_t dma_handle; void *dma_buffer = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); // 配置硬件DMA寄存器 start_dma_transfer(dma_handle); ``` - **中断处理**: ```c irqreturn_t my_interrupt(int irq, void *dev_id) { // 检查中断状态寄存器 if (is_io_complete()) { complete(&io_done); // 唤醒等待队列 return IRQ_HANDLED; } return IRQ_NONE; } ``` #### 4. **存储协议实现 - **SCSI/SATA/NVMe**:根据磁盘接口实现协议栈 - SCSI命令集:`scsi_command`结构体 - NVMe队列:创建提交/完成队列对 ```c struct nvme_queue *nvmeq = kzalloc(sizeof(*nvmeq), GFP_KERNEL); nvmeq->sq_dma_addr = dma_map_single(dev, sq, SQ_SIZE, DMA_TO_DEVICE); ``` #### 5. **调试与优化** - **调试工具**: - `printk`分级输出:`KERN_DEBUG`/`KERN_ERR` - Ftrace动态跟踪:`echo function > /sys/kernel/debug/tracing/current_tracer` - Crash工具分析内核转储 - **性能优化**: - 启用多队列(blk-mq):`blk_mq_init_queue()` - 实现IO调度合并:`elevator_merge_fn` - DMA预分配缓存池 #### 6. **硬件初始化(参考启动流程)[^5]** ```c void init_hardware(void) { // 1. 复位控制器 write_reg(CTRL_RESET, 0x1); // 2. 配置时钟 configure_pll(CLOCK_RATE); // 3. 初始化DDR控制器 init_ddr_controller(DDR_TIMING); // 4. 设置DMA引擎 setup_dma_channels(); // 5. 注册中断处理 request_irq(IRQ_VECTOR, my_isr, IRQF_SHARED, "mydisk", dev); } ``` #### 7. **开发建议** 1. 参考内核已有驱动:如`drivers/block/virtio_blk.c` 2. 使用内核基础设施:DMA API, IRQ 管理, 内存池 3. 遵循内核编码规范:`checkpatch.pl`检查 4. 重点处理错误路径:I/O超时、DMA故障、介质错误 5. 实现电源管理:`pm_ops`结构体支持休眠唤醒 > **重要提示**:真实磁盘驱动需严格处理**并发访问**(自旋锁/信号量)、**内存屏障**(`mb()`/`rmb()`)和**缓存一致性**问题,错误处理不当可能导致系统崩溃或数据损坏[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值