linux驱动开发之常见面试问题

linux驱动开发之常见面试问题

      • 新增驱动的基本操作
      • 寄存器基址怎么映射?
      • probe里的常规操作。
      • 驱动中通常会定义一个私有结构体,里面包含一些内核结构体,但注册的时候只注册了某个成员,怎么找到这个私有结构体。
        • 什么是container_of
      • 如何给应用层提供接口
        • 什么是ioctl?
      • 如何在shell下调用驱动
      • 如何解决内核启动时卡死问题
      • 简述MMU的工作原理

新增驱动的基本操作

先在设备树里新建一个节点,填写compatible和reg属性,然后在驱动里映射寄存器基址,后续就可以操作寄存器。
如果是linux发行版中没有的驱动
需要获取驱动源代码:

下载适用于你的内核版本的驱动源代码。
解压源代码:

使用tar命令解压下载的压缩包,如tar -xzvf driver.tar.gz
进入驱动目录:

使用cd命令切换到解压后的驱动目录。
配置驱动:

运行make configmake menuconfig命令配置驱动选项。
编译驱动:

运行make命令编译驱动。
安装驱动:

使用make install或手动将生成的模块文件复制到合适的目录,通常是/lib/modules//kernel/drivers
加载驱动:

运行modprobe driver_name加载驱动,或者使用insmod driver.ko命令。
检查驱动加载情况:

使用lsmod命令查看已加载的模块,确保新驱动在列表中。
配置自动加载:

将驱动名称添加到/etc/modules文件中,以便系统在启动时自动加载。
重启系统:

为了确保新驱动在系统启动时自动加载,最好重启系统。

寄存器基址怎么映射?

A:先用platform_get_resource获取IORESOURCE_MEM资源,然后用devm_ioremap_resource将基址映射为虚拟地址。

probe里的常规操作。

A:通常一个驱动都会有时钟,所以probe里映射完基址后,通常要进行时钟和复位的操作。调用devm_clk_get获取时钟源,然后调用devm_clk_prepare_enbale使能时钟,复位的话先调用devm_reset_control_get获取复位源,然后用reset_control_reset复位。

驱动中通常会定义一个私有结构体,里面包含一些内核结构体,但注册的时候只注册了某个成员,怎么找到这个私有结构体。

A:可以通过container_of宏找到这个私有结构体的指针。

什么是container_of

container_of 是 Linux 内核中一个常用的宏,用于从一个结构体中的某个字段获取该结构体的指针。这在实现容器数据结构时非常有用,尤其是在链表中。
具体的使用格式如下:

#include 

#define container_of(ptr, type, member) \
    ({ \
        const typeof(((type *)0)->member) *__mptr = (ptr); \
        (type *)((char *)__mptr - offsetof(type, member)); \
    })

其中:

ptr 是指向结构体中某个成员的指针。
type 是结构体的类型。
member 是结构体中的成员名。
这宏的作用是返回包含给定成员的结构体的指针。

这个宏在内核中广泛用于实现各种数据结构,尤其是在实现链表时,通过链表节点的成员指针可以找到整个结构体的指针。具体详细讲解可以自行百度。

如何给应用层提供接口

A:驱动给应用层提供接口,一般都是通过ioctl接口,应用层传入一个结构体,驱动解析

什么是ioctl?

ioctl(Input/Output Control)是Linux系统中的一个系统调用,用于对设备进行输入/输出的控制。它允许用户空间程序通过系统调用与设备驱动程序进行通信,提供一种灵活的设备控制机制。ioctl的使用方式是通过命令(或请求号)来指定要执行的操作,以及相应的参数。

基本的ioctl原型如下:

int ioctl(int fd, unsigned long request, ...);

fd 是文件描述符,指向要进行控制的设备或文件。
request 是命令或请求号,用于指定要执行的操作。
... 是可选参数,用于传递与请求相关的参数。

一些常见的ioctl使用场景包括:

  1. 设备设置:
    设置设备的一些特性,例如串口的波特率、数据位、停止位等。

  2. IO模式切换:
    控制设备的读写模式,例如切换到阻塞或非阻塞模式。

  3. 硬件信息查询:
    获取设备的硬件信息,例如查询磁盘的几何信息。

  4. 字符设备操作:
    对字符设备进行一些特定的操作,例如终端的清屏、设置光标位置等。

  5. 网络套接字设置:
    配置网络套接字的选项,例如设置套接字的超时时间、设置广播选项等。

  6. 图形设备控制:
    控制图形设备的一些操作,例如设置显示器的分辨率、颜色深度等。

使用ioctl需要查阅相关设备或系统调用的文档,以了解支持的命令和参数。在驱动程序的开发中,通常会实现相应的ioctl处理函数,用于处理不同的命令。

总体而言,ioctl提供了一个灵活的接口,使用户空间程序能够与内核中的设备驱动进行通信和控制。

如何在shell下调用驱动

A:可以提供procfssysfs或者debugfs这三个虚拟文件系统接口,根据具体用途提供

procfs接口一般是系统性的信息,主要是用来查看的,例如内存信息。
sysfs接口更多用于与驱动交互,可以传参给驱动,修改驱动中一些变量。
debugfs一般是用来调试用的,需要挂载debugfs

如何解决内核启动时卡死问题

A:卡住或者崩了,需要分析卡在哪里,从而找到原因。

在内核的initcall初始化函数中加打印,把initcalllevel函数指针打印出来,看内核跑到了哪个等级的初始化。

level等级是知道了,但这个等级执行的函数太多,而且打印出来的是地址,怎么知道具体跑到哪个函数?

A:把内核编译出来的vmlinux文件反汇编,反汇编文件包含函数名和对应地址,根据地址查找。

vmlinux 是 Linux 内核编译后的可执行文件,包含了完整的内核代码和数据。如果你想进行 vmlinux 文件的反汇编,可以使用工具如 objdump 或 gdb。

以下是使用 objdump 进行 vmlinux 反汇编的基本步骤:

objdump -D vmlinux > vmlinux_disassembly.txt

这将会生成一个包含反汇编代码的文本文件 vmlinux_disassembly.txt。你可以使用文本编辑器查看或搜索其中的代码。

如果你想在 gdb 中进行交互式的反汇编,可以按照以下步骤:

gdb vmlinux

在 GDB 中,你可以使用 disassemble 命令来查看反汇编代码:

(gdb) disassemble

你也可以指定地址范围来查看特定部分的代码:

(gdb) disassemble 0xffffffff81000000, 0xffffffff81010000

请注意,反汇编的结果可能会很庞大,因为它包含整个内核的代码。选择性地查看特定的函数或区域可能更有帮助。

内核调试方法?
A:printk、BUG_ON、devmem、dump_statck…

printk有打印等级,用法和printf一样,可以用pr_info、pr_err这些不同等级的函数加打印。在shell中可以通过echo的方式控制printk等级

devmem是一个命令,它可以在shell下直接读写寄存器
devmem应用程序会打开/dev/mem节点,这个设备实现了mmap接口,devmem应用程序打开/dev/mem后,会调用mmap函数将寄存器物理地址映射到用户空间,所以可以直接读写寄存器

dump_stack函数可以打印函数调用栈,可以分析函数调用关系
可以用工具链的add2line,查找地址对应的符号,就能看到函数名
内核配置打开CONFIG_KALLSYMS,这个配置是编入符号表,选上后,dump_stack就可以清楚看到调用关系和符合偏移。

简述MMU的工作原理

A:在一个三级页表的内存管理系统中,MMU(内存管理单元)通过访问页表基址寄存器(Page Table Base Register,简称PTBR)获取一级页表的基地址,然后通过虚拟地址中的一级页表索引(Page Global Directory Index,简称PGD index)找到相应的二级页表。

接着,MMU利用二级页表基址和虚拟地址中的二级页表索引(Page Table Entry Index,简称PTE index)找到相应的页表项(Page Table Entry,简称PTE)。在这个例子中,PTE 存储的是物理页框号(Page Frame Number,简称PFN),即该虚拟页对应的物理页框。

最后,MMU将物理页框号(PFN)与虚拟地址中的页内偏移相加,得到实际的物理地址。

这个过程可以总结为以下步骤:

MMU根据虚拟地址的高位来查找一级页表项(PGD),获取二级页表的基地址。
MMU再根据虚拟地址的中间位来查找二级页表项(PTE),获取物理页框号(PFN)。
MMU将物理页框号与虚拟地址的低位偏移相加,得到最终的物理地址。
这样,通过多级页表的层级结构,MMU能够实现对大型地址空间的映射和管理,从而提供了一种灵活、高效的内存管理方案。

页表基址寄存器存储第一级页表的基地址。
页表是软件创建的。MMU只是通过页表,将虚拟地址转换为了物理地址。
页表在物理内存中。
CPU先访问TLB,如果TLB中存在这个地址,则直接从TLB中取地址。如果没有,再访问内存,读取页表,将虚拟地址转为物理地址,从而访问到内存。
TLB在MMU中,本质是一块cache。

你可能感兴趣的:(Linux,内核驱动,C语言学习,linux,驱动开发,面试)