问题定位:mount: mounting /dev/mtdblock23 on /rootfs failed: Invalid argument
原有使用ubifs文件系统的分区,计划切换到squashfs。在镜像制作好之后,mount出现如下错误:
mount: mounting /dev/mtdblock23 on /rootfs failed: Invalid argument
1 定位前思考
操作流程如下:
- Buildroot生成rootfs.squashfs。
- 通过工具烧录到NAND分区。
- 在Linux环境下,挂载:mount -t squashfs /dev/mtdblock23 /rootfs。
预想定位流程:
- strace查看是哪个系统调用返回的Invalid argument。
- 内核的话:就需要往内核加调试代码。
- 导出镜像文件和原始rootfs.squashfs对比,查看读写是否有问题。
2 定位流程
2.1 查看返回Invalid argument执行流程
通过strace mount -t squashfs /dev/mtdblock23 /rootfs,确定是mount系统调用返回Invalid argument。
通过ftrace查看mount调用定位:
echo *sys_mount > /sys/kernel/debug/tracing/set_graph_function
echo function_graph > /sys/kernel/debug/tracing/current_trace
执行mount命令,然后查看函数调用关系:
cat /sys/kernel/debug/tracing/trace
Kernel的mount系统调用流程:
sys_mount
ksys_mount
copy_mount_string--拷贝mount的文件系统类型和设备名称到内核中。
copy_mount_options--拷贝mount选项到内核中。
do_mount
user_path_at
security_sb_mount
do_reconfigure_mnt
do_remount
do_loopback
do_change_type
do_move_mount_old
do_new_mount
get_fs_type
fs_context_for_mount
put_filesystem
vfs_parse_fs_string
parse_monolithic_mount_data
mount_capable
vfs_get_tree
squashfs_get_tree
squashfs_get_tree
get_tree_bdev
blkdev_get_by_path
sget_fc
squashfs_fill_super
kmem_cache_alloc_trace
sb_min_blocksize
squashfs_read_table
squashfs_read_data
__getblk_gfp
ll_rw_block
submit_bh_wbc
bio_alloc_bioset
bio_associate_blkg
bio_add_page
guard_bio_eod
submit_bio
generic_make_request
generic_make_request_checks
blk_queue_enter
blk_mq_make_request
__blk_mq_sched_bio_merge
blk_mq_get_request
blk_account_io_start
blk_mq_sched_insert_request
dd_insert_requests
blk_mq_run_hw_queue--大概在这里返回错误,怀疑是独到的block数据错误。
deactivate_locked_super
PS:通过strace粗略定位,然后通过function_graph达到函数级别定位,这个流程还是挺迅速的。
2.2 检查写入NAND数据正确性
首先可以在Ubuntu对rootfs.ubifs进行验证,验证原始数据正确性:
mount: mounting /dev/mtdblock23 on /rootfs failed: Invalid argument
进入rootfs查看,挂在是否正确。如果正确说明原始数据没有问题。
在启动的Linux环境中,检查分区内容是否正确:
- 通过dd读出/dev/mtdblock23内容,使用md5sum对比hash值:
dd if=/dev/mtdblock24 of=test.squashfs bs=1605632 count=1
- 通多hexedit打开/dev/mtdblock23,查看具体数据是否一致。
对比后发现通过工具写入的数据不正确,基本确定是工具问题。
然后通过如下命令写入,确认镜像是否正确:
dd if=rootfs.squashfs of=/dev/mtdblock24
进一步确定是工具问题。
3 定位后反思
- strace+function_graph是一个通用高效的定位内核问题的手段。
- 在定位前,要找准方向,不要在错误的方向上努力。对整个流程,疑问点有个全局的认知,再进行定位。
- 如果是mtdblock层的问题,定位就困难了,这块还需要加强。
- 回顾一下数据经过不同存储介质,应该优先检查数据在过程中的完整和正确。
4 /dev/mtdblock相关
4.1 /dev/mtd和/dev/mtdblock区别
在Linux系统中,/dev/mtd 和 /dev/mtdblock 是与MTD(Memory Technology Device)相关的设备文件,如NAND。以下是 /dev/mtd 和 /dev/mtdblock 的主要区别:
- 设备抽象级别:
- /dev/mtd:这些设备文件提供了对MTD设备的原始访问,通常用于直接与MTD设备交互,如nandwrite/nandump、flashcp/flash_crase等命令。参考《NAND/MTD/UBI/UBIFS概念及使用方法》。
- /dev/mtdblock:这些设备文件提供了对MTD设备的块设备抽象,允许将MTD设备作为块设备来访问,这使得它们可以被挂载为文件系统。
- 使用场景:
- /dev/mtd:通常用于底层的设备驱动程序和需要直接访问MTD设备的场景,如嵌入式系统或特定的应用程序。
- /dev/mtdblock:适用于需要将MTD设备作为文件系统存储设备的场景,如用于存储操作系统、应用程序或用户数据。
- 分区和文件系统:
- /dev/mtd:不支持分区或文件系统,它们是按字节寻址的,通常不用于存储文件系统。
- /dev/mtdblock:可以被分区并挂载文件系统,如JFFS2、YAFFS2、SQUASHFS等,适用于需要文件系统功能的场景。
- 设备节点:
- /dev/mtd:设备节点通常以 /dev/mtdX 的形式存在,其中 X 是设备编号。
- /dev/mtdblock:设备节点通常以 /dev/mtdblockX 的形式存在,其中 X 是设备编号。
- 驱动程序:
- /dev/mtd:由MTD子系统的驱动程序管理,如 mtdchar 驱动。
- /dev/mtdblock:由 mtdblock 驱动管理,该驱动在MTD设备之上模拟块设备。
- 访问方式:
- /dev/mtd:通常需要使用特定的MTD工具或API来访问,如 mtdinfo、mtdpart 等。
- /dev/mtdblock:可以使用标准的块设备操作来访问,如 mount、umount、dd 等。
- 性能:
- /dev/mtd:由于是直接访问,可能提供更好的性能,尤其是在需要频繁擦除和写入的场景。
- /dev/mtdblock:性能可能受到文件系统和块设备抽象的影响,但对于文件系统操作来说,这种方式更直观和方便。
总的来说,/dev/mtd 和 /dev/mtdblock 提供了不同层次的抽象,适用于不同的使用场景。开发者应根据具体需求选择合适的访问方式。
4.2 /dev/mtdblock框架
/dev/mtdx对应的驱动为drivers/mtd/mtdchar.c:
init_mtdchar
__register_chrdev--字符设备操作函数集为mtd_fops。对/dev/mtdx的操作,对应如下函数。
mtdchar_open
get_mtd_device--获取struct mtd_info结构体。
mtdchar_unlocked_ioctl
mtdchar_ioctl
mtdchar_read
mtd_read_oob/mtd_read
mtdchar_write
mtd_write_oob/mtd_write
mtdchar_mmap
vm_iomap_memory
mtdchar_close
put_mtd_device
/dev/mtdblockx的驱动在drivers/mtd/mtdblock.c中:
init_mtdblock
register_mtd_blktrans--注册mtdblock_tr操作函数集作为block层到mtd层的转换层。
register_blkdev--注册block设备。
mtd_for_each_device--遍历mtd设备调用struct mtd_blktrans_ops的add_mtd函数,创建block到mtd的转换设备。
mtdblock_tr是block到mtd转换操作函数集:
static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", .major = MTD_BLOCK_MAJOR, .part_bits = 0, .blksize = 512, .open = mtdblock_open, .flush = mtdblock_flush, .release = mtdblock_release, .readsect = mtdblock_readsect,
do_cached_read
mtd_read .writesect = mtdblock_writesect,
do_cached_write
mtd_write .add_mtd = mtdblock_add_mtd, .remove_dev = mtdblock_remove_dev, .owner = THIS_MODULE, };
mtdblock_add_mtd建立了block和mtd层之间的连接:
mtdblock_add_mtd
->创建struct mtdblk_dev设备,其中mbd建立了mtd和tr之间的联系。tr中的major和name关联到mtdblock类型block。
->add_mtd_blktrans_dev
->list_add_tail--将新建的struct mtd_blktrans_dev加入到struct mtdblktrans_ops的devs列表中。
->alloc_disk--分配一个struct gendisk。然后对其初始化,并且操作函数集为mtd_block_ops。
->blk_mq_init_sq_queue--创建gendisk的struct request_queue,操作函数集为mtd_mq_ops。
->blk_queue_logical_block_size
->blk_queue_flag_set
->blk_queue_flag_clear
->device_add_disk--创建gendisk设备。
->sysfs_create_group
大致框架结构如下: