linux驱动开发之常碰面试问题
新增驱动的基本操作
先在设备树里新建一个节点,填写compatible和reg属性,之后在驱动里映射寄存器基址,后续就可以操作寄存器。
若果是linux发行版中没有的驱动
须要获取驱动源代码:
下载适用于你的内核版本的驱动源代码。
解压源代码:
使用tar命令解压下载的压缩包,如tar-xzvfdriver.tar.gz。
步入驱动目录:
使用cd命令切换到解压后的驱动目录。
配置驱动:
运行makeconfig或makemenuconfig命令配置驱动选项。
编译驱动:
运行make命令编译驱动。
安装驱动:
使用makeinstall或自动将生成的模块文件复制到合适的目录,一般是/lib/modules//kernel/drivers。
加载驱动:
运行modprobedriver_name加载驱动,或则使用insmoddriver.ko命令。
检测驱动加载情况:
使用lsmod命令查看已加载的模块,确保新驱动在列表中。
配置手动加载:
将驱动名称添加到/etc/modules文件中,便于系统在启动时手动加载。
重启系统:
为了确保新驱动在系统启动时手动加载,最好重启系统。
寄存器基址如何映射?
A:先用platform_get_resource获取IORESOURCE_MEM资源,之后用devm_ioremap_resource将基址映射为虚拟地址。
probe里的常规操作。
A:一般一个驱动就会有时钟,所以probe里映射完基址后,一般要进行时钟和复位的操作。调用devm_clk_get获取时钟源linux内核开发,之后调用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是结构体中的成员名。
这宏的作用是返回包含给定成员的结构体的表针。
这个宏在内核中广泛用于实现各类数据结构,尤其是在实现数组时linux就该这么学,通过数组节点的成员表针可以找到整个结构体的表针。具体详尽讲解可以自行百度。
怎样给应用层提供插口
A:驱动给应用层提供插口,通常都是通过ioctl插口,应用层传入一个结构体,驱动解析
哪些是ioctl?
ioctl(Input/OutputControl)是Linux系统中的一个系统调用,用于对设备进行输入/输出的控制。它容许用户空间程序通过系统调用与设备驱动程序进行通讯,提供一种灵活的设备控制机制。ioctl的使用方法是通过命令(或恳求号)来指定要执行的操作,以及相应的参数。
基本的ioctl原型如下:
int ioctl(int fd, unsigned long request, ...);
fd是文件描述符,指向要进行控制的设备或文件。
request是命令或恳求号,用于指定要执行的操作。
…是可选参数,用于传递与恳求相关的参数。
一些常见的ioctl使用场景包括:
设备设置:
设置设备的一些特点,比如并口的码率、数据位、停止位等。
IO模式切换:
控制设备的读写模式,比如切换到阻塞或非阻塞模式。
硬件信息查询:
获取设备的硬件信息,比如查询c盘的几何信息。
字符设备操作:
对字符设备进行一些特定的操作,比如终端的清屏、设置光标位置等。
网路套接字设置:
配置网路套接字的选项,比如设置套接字的超时时间、设置广播选项等。
图形设备控制:
控制图形设备的一些操作,比如设置显示器的帧率、颜色深度等。
使用ioctl须要查阅相关设备或系统调用的文档,以了解支持的命令和参数。在驱动程序的开发中,一般会实现相应的ioctl处理函数,用于处理不同的命令。
总体而言,ioctl提供了一个灵活的插口,使用户空间程序才能与内核中的设备驱动进行通讯和控制。
怎样在shell下调用驱动
A:可以提供procfs、sysfs或则debugfs这三个虚拟文件系统插口,依据具体用途提供
procfs插口通常是系统性的信息,主要是拿来查看的,比如显存信息。
sysfs插口更多用于与驱动交互,可以传参给驱动,更改驱动中一些变量。
debugfs通常是拿来调试用的,须要挂载debugfs。
怎么解决内核启动时卡死问题
A:卡住或则崩了,须要剖析卡在那里,因而找到缘由。
在内核的initcall初始化函数中加复印,把initcall的level和函数表针复印下来,看内核挪到了那个等级的初始化。
level等级是晓得了,但这个等级执行的函数太多linux软件,但是复印下来的是地址,如何晓得具体挪到那个函数?
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,这个配置是编入符号表,选上后linux内核开发,dump_stack就可以清楚看见调用关系和符合偏斜。
阐述MMU的工作原理
A:在一个五级页表的显存管理系统中,MMU(显存管理单元)通过访问页表基址寄存器(PageTableBaseRegister,简称PTBR)获取一级页表的基地址,之后通过虚拟地址中的一级页表索引(PageGlobalDirectoryIndex,简称PGDindex)找到相应的二级页表。
接着,MMU借助二级页表基址和虚拟地址中的二级页表索引(PageTableEntryIndex,简称PTEindex)找到相应的页表项(PageTableEntry,简称PTE)。在这个反例中,PTE储存的是化学页框号(PageFrameNumber,简称PFN),即该虚拟页对应的数学页框。
最后,MMU将化学页框号(PFN)与虚拟地址中的页内偏斜相乘,得到实际的化学地址。
这个过程可以总结为以下步骤:
MMU按照虚拟地址的低位来查找一级页表项(PGD),获取二级页表的基地址。
MMU再按照虚拟地址的中间位来查找二级页表项(PTE),获取数学页框号(PFN)。
MMU将数学页框号与虚拟地址的高位偏斜相乘,得到最终的数学地址。
这样,通过多级页表的层级结构,MMU就能实现对小型地址空间的映射和管理,因而提供了一种灵活、高效的显存管理方案。
页表基址寄存器储存第一级页表的基地址。
页表是软件创建的。MMU只是通过页表,将虚拟地址转换为了数学地址。
页表在化学显存中。
CPU先访问TLB,假如TLB中存在这个地址,则直接从TLB中取地址。假如没有,再访问显存,读取页表,将虚拟地址转为化学地址,因而访问到显存。
TLB在MMU中,本质是一块cache。