Linux驱动开发
1 设备驱动
1、请简述主设备号和次设备号的用途。
主设备号用于标识设备所属的驱动程序,而次设备号则用于标识同一类型设备中的不同实例。
它们共同提供了对设备的有效管理和访问。
2、字符型驱动设备怎么创建设备文件?
- 使用mknod命令手动创建设备文件
ls -l /dev // 检查当前系统中是否已存在同名的字符设备文件
sudo mknod <设备文件的路径和名称> C <主设备号> <次设备号> //如果不存在同名的字符设备文件
- 使用Udev来自动创建
Udev是一个在Linux下动态管理设备的设备管理器,它负责监听设备的插拔事件,并根据事先定义的规则来创建、删除和管理设备节点。
3、设备驱动程序中如何注册一个字符设备?分别解释一下它的几个参数的含义。
注册一个字符设备驱动有两种方法:
void cdev_init(struct cdev *cdev, struct file_operations *fops)
该注册函数可以将cdev结构嵌入到自己的设备特定的结构中。cdev是一个指向结构体cdev的指
针,而fops是指向一个类似于f file_operations结构(可以是file_operations结构,但不限于该结
构)的指针。int register_chrdev(unsigned int major, const char *namem , struct file operations *fopen);
该注册函数是早期的注册函数,major是设备的主设备号,name是驱动程序的名称,而fops是默
认的file_operations结构(这是只限于file_operations结构)。对于register_chrdev的调用将为给
定的主设备号注册0-255作为次设备号,并为每个设备建 立一个对应的默认cdev结构。
4、/dev/下面的设备文件是怎么创建出来的?
有三种方式:
devfs机制(应该是2.6以前的内核使用的),
udev机制:其实就是现在常用的device_create()、class_create()这一套接口。
使用mknod手动创建设备节点。
5、Linux设备中字符设备和块设备有什么主要区别?
字符设备和块设备的主要区别在于数据访问方式和应用场景。字符设备以字符为单位读写,适用于顺序流式的输入输出设备,而块设备以固定大小的块进行读写,适用于需要随机访问和高性能数据操作的设备。
6、驱动中操作物理绝对地址为什么要先ioremap?
ioremp是内核中用来将外设寄存器物理地址映射到主存上去的接口,即将io地址空间映射到虚拟地址空间上去,便于操作。
为什么非要映射呢?因为保护模式下的cpu只认虚拟地址,不认物理地址,给它物理地址它并不帮你做事,所以你要操作外设上的寄存器必须先映射到虚拟内存空间,拿着虚拟地址去跟cpu对接,从而操作寄存器。
7、insmod,rmmod一个驱动模块,会执行模块中的哪个函数?在设计上要注意哪些问题?
分别会执行 module_init() 和 module_exit() 指定的init函数和exit函数。
要注意的就是,尽量对init函数中出现的资源申请及使用时,都要有对应的释放操作在exit中,即init申请,eixt释放。
8、NAND驱动的probe流程
注册驱动:使用nand_register()
函数
设备匹配:当内核在启动过程中发现新的设备时,会尝试将其与已注册的驱动程序进行匹配。
初始化硬件:一旦设备匹配成功,驱动程序的probe()
函数将被调用。在probe()
函数中,驱动程序会执行相应的初始化操作。
内存分配:接下来,驱动程序需要为NAND设备的数据传输和缓存分配内存空间。
识别设备:在初始化和内存分配完成后,驱动程序会向NAND设备发送命令来识别设备型号和参数。
驱动注册:如果设备识别成功,并且硬件和软件配置都正确,驱动程序会将其与内核关联起来。这意味着驱动程序会在/dev
目录下创建一个设备文件,并建立与NAND设备的通信通道。
9、Linux驱动开发中,常用的调试方法有哪些?
利用printk
使用kgdb
10、linux设备和驱动如何匹配?
如果系统用了设备树,那就是比较compatible属性,设备节点和驱动匹配成功后自动执行prob函数。
如果没用设备树,那就是比较name字段,实际两者比较的都是字符串,相当于名字一样算匹配成功
2 Linux内核
1、内核中申请内存有哪几个函数?有什么区别?
Linux内核中常用的申请内存的函数有:kmalloc()、vmalloc()、get_free_pages() 和 ioremap()。
- kmalloc()适合分配较小的内存块,以字节为单位进行分配,返回连续的内存块指针。
- vmalloc()用于动态申请较大的虚拟内存,适合分配几百KB到数GB的内存块,不要求内存的连续性。
- get_free_pages()用于从物理内存中分配连续的内存页,以页面(通常是4KB)为单位进行分配,适合较大的内存块。
- ioremap用于将设备的IO空间映射到内核地址空间,以便CPU直接访问外部设备。
选择合适的内存分配函数要根据具体需求和内存大小来决定,以提高内核的性能和效率。
2、怎样申请大块内核内存?
使用vmalloc函数来动态地分配较大的虚拟内存块。
3、什么是内核空间,用户空间?
内核空间和用户空间是操作系统中的两个互相隔离的内存区域。
- 内核空间是操作系统内核运行的区域,具有最高的特权和权限,可以直接访问底层硬件和资源,执行核心任务如设备驱动程序和内存管理。
- 用户空间是用于运行应用程序和普通用户代码的区域,拥有较低的权限和特权,无法直接访问底层硬件,需要通过系统调用接口与内核进行交互。
内核空间和用户空间之间通过上下文切换实现转换,上下文切换是将控制权从用户空间切换到内核空间,让内核执行相应操作,然后再切换回用户空间继续执行应用程序。这种设计确保了操作系统核心的稳定性和安全性。
4、为什么需要区分内核空间与用户空间?
- 限制不同程序之间的资源访问,防止它们互相干扰。
- 增强系统的安全性,降低恶意应用程序或用户对系统构成的潜在威胁。
- 提高系统的稳定性,因为这样就能减少错误或恶意应用程序对核心的不良影响。
5、什么是内核态和用户态?
内核态和用户态是操作系统中不同程序或进程所处的不同运行模式。
- 内核态是操作系统的最高权限模式,只有操作系统内核可以执行。在内核态下,可以执行特权指令、访问系统资源和控制硬件设备。
- 用户态是应用程序或用户代码所执行的模式。在用户态下,程序只能访问受限的资源,不能直接访问系统资源或进行特权操作。
内核态和用户态的区分目的是保护操作系统的安全性、稳定性和资源隔离。通过将核心功能和敏感操作限制在内核态中,可以防止应用程序直接访问操作系统核心,对系统进行了一定的保护。同时,将应用程序限制在用户态下,可以提供资源隔离和访问控制,确保不同程序之间的合理竞争和资源管理。
6、用户空间与内核通信方式有哪些?
用户空间和内核之间进行通信的方式包括:
- 系统调用:用户程序通过系统调用接口向内核请求服务或功能。
- 中断和异常:用户程序可以触发中断或异常,将控制权转移到内核的中断处理程序或异常处理程序中。
- 进程间通信(IPC)机制:通过管道、消息队列、共享内存等方式实现不同进程之间的数据传递和同步操作。
- 文件系统:用户程序通过文件系统调用请求内核对文件进行操作。
- 设备文件:用户程序通过读写设备文件与内核中的硬件设备进行通信。
这些通信方式使得用户程序能够与内核进行有效交互,获取所需的服务和资源。选择适当的通信方式可以提高系统的性能和可靠性。
7、内核链表为什么具有通用性?
内核链表具有通用性主要因为它可以灵活存储各种类型的数据、内核链表的大小可以动态扩展或缩小,可以根据需要添加或删除节点。内核链表的插入和删除操作具有较低的时间复杂度。内核链表的实现相对简单,容易理解和使用。内核链表可以根据需要进行扩展,例如双向链表、循环链表等。这使得内核链表在不同场景下都能应用,并方便扩展满足需求。
8、应用程序中open()在linux中执行过程中是如何从用户空间到内核空间?
应用程序首先调用 open()
函数,在用户空间中执行。
open()
函数内部会触发系统调用,将控制权从用户空间切换到内核空间。
系统调用将参数传递给内核,内核根据提供的文件路径和打开选项,对文件进行访问。
内核处理完打开文件的操作后,会返回一个有效的文件描述符给用户空间。
3 中断
1、硬中断/软中断是什么?有什么区别?
硬中断是由硬件设备触发的中断,响应快,但处理简单;
而软中断是由操作系统或软件驱动程序触发的中断,响应相对较慢,但可以执行更复杂的操作。
2、中断为什么要区分上半部和下半部?
中断的区分上半部和下半部是为了提高系统响应性能和可靠性。
上半部是中断服务例程,负责进行必要的硬件处理和紧急操作,执行时中断无法被屏蔽或延迟。
下半部是延迟处理阶段,通过将一些耗时、复杂的操作延迟到稍后执行,避免长时间占用中断服务例程,提高系统的响应性能,并确保中断的快速释放,使整个系统更加可靠。
3、中断下半部一般如何实现?
中断下半部是指在中断处理函数执行完后,为了避免长时间占用中断服务例程以及提高系统的响应性能,将一些较耗时或不适合在中断上下文中执行的操作延迟到稍后执行的机制。
使用软中断、tasklet、工作队列。
软中断: 通过注册处理函数,在适当时机由内核调度执行,适合简单操作;
tasklet: 是基于软中断的机制,执行时间短,适合中等复杂操作;
工作队列:是基于内核线程的异步机制,适合处理复杂、耗时较长的操作。
4、Linux中断的响应执行流程?中断的申请及何时执行(何时执行中断处理函数)?
中断响应流程:CPU接收中断->保存中断上下文->跳转到中断处理例程->执行中断上半部->执行中断下半部->恢复中断上下文。
中断的申请的正确位置:应该是在第一次打开 、硬件被告知终端之前。
4 文件系统
1、什么是根文件系统?
根文件系统是整个文件系统的起点,为操作系统提供了必要的环境和资源。
作为文件系统层次结构的最顶层,包含了操作系统所需的基本文件和目录。
2、根文件系统为什么这么重要?
首先,它包含了操作系统所需的核心文件和目录,提供了操作系统正常运行所需的基本功能和工具;
其次,根文件系统存放着用户数据和配置文件,确保用户数据的存储和访问;
最后,通过根文件系统的配置和定制,可以实现对操作系统的功能扩展和定制化,满足特定的需求和应用场景。
3、可执行映像文件通常由几部分构成,它们有什么特点?
可执行映像文件通常由头部、文本段、数据段和BSS段组成。
头部提供文件的描述信息,文本段包含执行指令,数据段存储静态数据,BSS段存储未初始化的变量。
这些部分协同工作,使得可执行映像文件能够被操作系统正确加载和执行。
5 Uboot
什么是bootloader?
引导加载程序(bootloader)是计算机开机后最先运行的软件,负责初始化硬件、加载操作系统,并将控制权交给操作系统。它的作用就像一个桥梁,连接硬件和操作系统,确保计算机能够正确启动并正常工作。
uboot启动过程中做了哪些事?
第一阶段
初始化时钟,关闭看门狗,关中断,启动ICACHE,关闭DCACHE和TLB,关闭MMU,初始化SDRAM,
初始化NAND FLASH,重定位。
第二阶段
初始化一个串口,检测系统内存映射,将内核映象和根文件系统映象从 Flash上读到SDRAM空间中,为
内核设置启动参数,调用内核。
uboot和内核如何完成参数传递?
U-Boot和内核之间可以通过寄存器传递参数,其中R0、R1和R2是常用的寄存器。
R0寄存器:通常用于保存函数返回值或错误码。【在启动过程中,U-Boot可以将参数传递给内核并将返回值保存在R0寄存器中。】
R1寄存器:用于传递机器类型ID。【机器类型ID是一个特定平台的标识符,用于告诉内核当前运行的硬件环境。内核可以根据机器类型ID执行不同的硬件初始化操作。】
R2寄存器:用于传递块内存的基地址。【这块内存通常由U-Boot分配,在其中存放了一些特定参数,例如设备树地址、命令行参数等。内核可以在启动过程中读取这些参数,并进行相应的配置和初始化。】
为什么要给内核传递参数呢?
在此之前,uboot已经完成了硬件的初始化,可以说已经”适应了“这块开发板。然而,内核并不是对于所
有的开发板都能完美适配的(如果适配了,可想而知这个内核有多庞大,又或者有新技术发明了,可以
完美的适配各种开发板),此时,对于开发板的环境一无所知。所以,要想启动Linux内核,uboot必须
要给内核传递一些必要的信息来告诉内核当前所处的环境。
为什么uboot要关掉caches?
caches是cpu内部的一个2级缓存,它的作用是将常用的数据和指令放在cpu内部。caches是通过CP15
管理的,刚上电的时候,cpu还不能管理caches。上电的时候指令cache可关闭,也可不关闭,但数据
cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM
中数据还没有caches过来,导致数据预取异常 。
2 Linux驱动开发常用函数
ioremap:将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问。
copy_to_user:从内核空间中读取数据到用户空间。
copy_from_user:从用户空间中读取数据到内核空间。
6 常用的驱动开发指令
加载驱动:insmod/modprobe
卸载驱动:rmmod
查看驱动模块中打印信息:dmesg
查看内核中已有的字符设备信息:cat /proc/devices
查看正在使用的有哪些中断号:cat /proc/interrupt
7 常用的Linux指令
查看当前进程: ps
执行退出: exit
查看当前路径: pwd
创建新的空文件:touch file2.txt
向文件写内容:echo “this is a new file” > file2.txt
查看文件内容:cat file2.txt
查找文件 : find . -name “*.c” / find . -ctime -20
查找文件内容: grep -r update /etc/acpi #查找指定目录/etc/acpi 及其子目录(如果存在子目录的话)下所有文件中包含字符串"update"的文件
8 常用的GDB调试指令
gcc -g test.c -o test #编译时生成debug有关的程序信
gdb test #启动调试
run #重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r
start #单步执行,运行程序,停在第一执行语句
break+num #在第num行设置断点,简写b
enable breakpoints #启用断点
disable breakpoints #禁用断点
info breakpoints #查看当前设置的所有断点
delete breakpoints num #删除第num个断点,简写d
continue #继续运行,简写c
step #单步调试(逐语句:跳入自定义函数内部执行),简写s
next #单步调试(逐过程,函数直接执行),简写n
quit #退出gdb,简写q
面试题
链表和树有什么区别?
- 结构:
- 链表是由一系列节点组成的数据结构,每个节点包含数据和指向下一个节点的指针(或者双向链表还包含指向前一个节点的指针)。
- 树是由一系列节点组成的分层数据结构,每个节点包含数据和指向子节点的指针。
- 组织方式:
- 链表的节点按照插入顺序排列,没有特定的顺序关系。
- 树的节点按照层次关系组织,每个节点可以有零个或多个子节点。
- 数据访问:
- 链表只能从头节点开始遍历,沿着指针一个一个地访问节点,直到达到目标节点。
- 树可以通过不同的遍历方式(如先序、中序、后序、层序等)来访问节点,可以更灵活地处理树结构中的数据。
- 搜索效率:
- 在链表中进行搜索操作时,需要逐个遍历节点来寻找目标节点,时间复杂度为 O(n)。
- 在树中进行搜索操作时,可以利用树的结构特点,通过比较节点的值,可以快速定位到目标节点,搜索效率较高,时间复杂度通常为 O(log n)。
- 功能和应用:
- 链表通常用于实现动态数据结构,如栈、队列等。在内存中的分配是灵活的,但难以进行随机访问。
- 树常用于组织层次化的数据,如文件系统、数据库索引等。树的结构可以方便地进行插入、删除和搜索操作。
排序讲一下,复杂度是多少?
如何加载内核?
加载内核的过程是通过引导程序(Bootloader)完成的。
设备树讲一下?
用来描述硬件设备及其在计算机系统中的连接和配置的数据结构。
设备树的使用过程一般包括以下几个步骤:
- 编写设备树描述文件(通常是以.dts或.dtsi为后缀的文件),描述硬件设备和连接关系。
- 使用设备树编译工具(如dtc)将设备树描述文件编译生成二进制设备树文件(.dtb或.dtb.img)。
- 在启动时,使用引导加载程序(Bootloader)将设备树文件加载到内存中的固定位置。
- 内核启动时,在初始化过程中解析并使用设备树文件,根据其中的信息自动配置和初始化硬件设备。
数组和数组名取地址有区别吗?分别加1有什么区别?
数组和数组名取地址的结果是不同的,数组名取地址表示整个数组的首地址,而通过数组名加上元素下标取地址可以得到数组中每个元素的地址。在加1时,对于指向元素的指针,加1表示增加一个元素的内存大小;对于指向数组的指针,加1表示增加一个数组的大小。