Linux驱动笔试知识

1.Linux设备中字符设备与块设备有什么主要的区别?请分别列举一些实际的设备说出它们是属于哪 一类设备

字符设备字符设备是个能够像字节流(类似文件)一样被访问的设备,字符设备驱动程序通常至少实现open,close,read和write系统调用。字符终端、串口、鼠 标、键盘、摄像头、声卡和显卡等就是典型的字符设备

块设备和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备上能够容纳文 件系统,如:u盘,SD卡,磁盘等,可以任意存取字节数的字符设备

网络设备可以是一个硬件设备,或者是软件设备,他没有相应的read write,它是面向流的一种特殊设备。

2.驱动的加载过程

驱动的加载分为静态,和动态加载

静态加载是指把驱动直接编译到内核中,这种加载方式比较麻烦,特别是我们需要修改驱动的时候,就需要每次都编译内核,编译内核是比较费时间的

动态加载是指我们使用insmod命令,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量

3、Linux中引入模块机制有什么好处?

首先,模块是预先注册自己以便服务于将来的某个请求,然后他的初始化函数就立即结束。换句话 说,模块初始化函数的任务就是为以后调用函数预先作准备。

好处:

  1. 应用程序在退出时,可以不管资源的释放或者其他的清除工作,但是模块的退出函数却必须仔细此

撤销初始化函数所作的一切。

  1. 该机制有助于缩短模块的开发周期。即:注册和卸载都很灵活方便。

4、copy_to_user()和copy_from_user()主要用于实现什么功能?一般用于file_operations结构的哪 些函数里面?

由于内核空间和用户空间是不能互相访问的,如果需要访问就必须借助内核函数进行数据读写。

copy_to_user():完成内核空间到用户空间的复制,copy_from_user():是完成用户空间到内核空间的

复制。一般用于file_operations结构里的read,write,ioctl等内存数据交换作用的函数。当然,如果

ioctl没有用到内存数据复制,那么就不会用到这两个函数。

5、请简述主设备号和次设备号的用途。

如果执行mknod chartest c 4 64,创建chartest设备。请分 析chartest使用的是那一类设备驱动程序。

1)主设备号:主设备号标识设备对应的驱动程序。虽然现代的linux内核允许多个驱动程序共享主设

备号,但我们看待的大多数设备仍然按照“一个主设备对应一个驱动程序”的原则组织。

次设备号:次设备号由内核使用,用于正确确定设备文件所指的设备。依赖于驱动程序的编写方式,

我们可以通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索

引。

2)chartest 由驱动程序4管理,该文件所指的设备是64号设备。(感觉类似于串口终端或者字符设备

终端)。

6、设备驱动程序中如何注册一个字符设备?分别解释一下它的几个参数的含义。

注册一个字符设备驱动有两种方法:

**1) void cdev_init(struct cdev cdev, struct file_operations fops)

该注册函数可以将cdev结构嵌入到自己的设备特定的结构中。cdev是一个指向结构体cdev的指针,而

fops是指向一个类似于file_operations结构(可以是file_operations结构,但不限于该结构)的指

针.

**2) 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结构。

7.编写一个字符设备的流程

1.分配设备号

alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

2.初始化结构体

cdev_init(struct cdev *cdev, const struct file_operations *fops)

3.添加到内核中

cdev_add(struct cdev *p, dev_t dev, unsigned count)

4.创建一个类

class_create(owner, name)

5.创建设备文件

device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …)

8.请简述中断于DMA的区别。Linux设备驱动程序中,使用哪个函数注册和注销中断处理程序?

1)DMA:是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制,使用DMA 可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率.

中断:是指CPU在执行程序的过程中,出现了某些突发事件时CPU必须暂停执行当前的程序,转去处理

突发事件,处理完毕后CPU又返回源程序被中断的位置并继续执行。

所以中断和DMA的区别就是DMA不需CPU参与而中断是需要CPU参与的。

2)中断注册函数和中断注销函数

注册中断:

int request_irq(unsigned int irq, irqreturn_t (handler)(int, void , struct pt_regs *),

unsigned long flags, const char *dev_name, void *dev_id);

参数意义依次是:中断号,中断处理函数,中断管理有关的掩码,中断请求设备名,中断信号线。

过程是:dev_name设备请求中断->cpu分配中断号->设置中断管理的掩码->分配中断信号线->处理中断 函数->完成之后再根据设置情况返回原处理程序处继续处理程序。

注销中断

Void free_irq(unsigned int irq, void *dev_id);

9.写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?

中断处理例程应该尽量短,把能放在后半段(tasklet,等待队列等)的任务尽量放在后半段。

写一个中断服务程序要注意快进快出,在中断服务程序里面尽量快速采集信息,包括硬件信息,然后

退出中断,要做其它事情可以使用工作队列或者tasklet方式。也就是中断上半部和下半部。

第二:中断服务程序中不能有阻塞操作。应为中断期间是完全占用CPU的(即不存在内核调度),中断

被阻塞住,其他进程将无法操作;

第三:中断服务程序注意返回值,要用操作系统定义的宏做为返回值,而不是自己定义的OK,FAIL之

类的。

10.自旋锁和信号量在互斥使用时需要注意哪些?在中断服务程序里面的互斥是使用自旋锁还是信号 量?还是两者都能用?为什么?

使用自旋锁的进程不能睡眠,使用信号量的进程可以睡眠。

中断服务例程中的互斥使用的是自旋锁,原因是在中断处理例程中,硬中断是关闭的;但是要注意这

样会丢失可能到来的中断。

11.原子操作你怎么理解?为了实现一个互斥,自己定义一个变量作为标记来作为一个资源只有一个 使用者行不行?

原子操作指的是无法被打断的操作。 atomic_t

第二句话的意思是:

定义一个变量,比如 int flag =0;

 if(flag == 0) 

 { 

    flag = 1; 

     操作临界区; 

     flag = 0; 

 }

12.insmod 一个驱动模块,会执行模块中的哪个函数?rmmod呢?这两个函数在设计上要注意哪些? 遇到过卸载驱动出现异常没?是什么问题引起的?

insmod调用init函数,rmmod调用exit函数。

这两个函数在设计时要注意什么?

卸载模块时曾出现 卸载失败的情形,原因是存在进程正在使用模块,检查代码后发现产生了死锁的问题

要注意在init函数中申请的资源在exit函数中要释放,包括存储,ioremap,定时器,工作队列等

等。也就是一个模块注册进内核,退出内核时要清理所带来的影响,带走一切不留下一点痕迹。

13.驱动中操作物理绝对地址为什么要先ioremap?

因为内核没有办法直接访问物理内存地址,必须先通过ioremap获得对应的虚拟地址

14.设备驱动模型三个重要成员是?platfoem总线的匹配规则是?在具体应用上要不要先注册驱动再 注册设备?有先后顺序没?

设备驱动模型三个重要成员是 总线、设备、驱动

platfoem总线的匹配规则是:要匹配的设备和驱动都要注册;

15.linux内核里面,内存申请有哪几个函数,各自的区别?

kmalloc()

__get_free_page()

vmalloc()

kmalloc保证分配的内存在物理上是连续的,kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大

vmalloc保证的是在虚拟地址空间上的连续

内存只有在要被DMA访问的时候才需要物理上连续

vmalloc比kmalloc要慢

16.IRQ和FIQ有什么区别,在CPU里面是是怎么做的?

IRQ(Interrupt Request):指中断模式。
FIQ(Fast Interrupt Request):指快速中断模式。
IRQ与FIQ是ARM处理器的两种不同编程模式(ARM有7种处理模式)。

  1. 对FIQ你必须进快处理中断请求,并离开这个模式。
  2. IRQ可以被FIQ所中断,但FIQ不能被IRQ所中断,在处理FIQ时必须要关闭中断。
  3. FIQ的优先级比IRQ高。
  4. FIQ模式下,比IRQ模式多了几个独立的寄存器。

不要小看这几个寄存器,ARM在编译的时候,如果你FIQ中断处理程序足够用这几个独立的寄存器来运作,它就不会进行通用寄存器的压栈,这样也省了一些时间。

  1. FIQ的中断向量地址在0x0000001C,而IRQ的在0x00000018。(也有的在FFFF001C以及FFFF0018)

写过完整汇编系统的都比较明白这点的差别,18只能放一条指令,为了不与1C处的FIQ冲突,这个地方只能跳转,而FIQ不一样,1C以后没有任何中断向量表了,这样可以直接在1C处放FIQ的中断处理程序,由于跳转的范围限制,至少少了一条跳转指令。

  1. IRQ和FIQ的响应延迟有区别

IRQ的响应并不及时,从Verilog仿真来看,IRQ会延迟几个指令周期才跳转到中断向量处,看起来像是在等预取的指令执行完。FIQ的响应不清楚,也许比IRQ快。

17.中断的上半部分和下半部分的问题:讲下分成上半部分和下半部分的原因,为何要分?讲下如何 实现?

上半部分执行与硬件相关的处理要求快, 而有些驱动在中断处理程序中又需要完成大量工作,这构成矛 盾,所以Linux有所谓的bottom half机制,中断处理程序中所有不要求立即完成的,在开中断的环境下, 由底半程序随后完成.

拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内 核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干 的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处 理函数里面马上执行,可以将中断让出来做更紧迫的事情

Linux的底半处理实际上是建立在内核的软中断机制上的.如何实现该机制?

两种方式

【tasklet 工作队列】

1.定义和初始化

struct tasklet_struct tlet;

tasklet_init(&tlet, jit_tasklet_fn, (unsigned long) data);

参数

第一个:定义的tasklet变量

第二个:函数

第三个:数据 传递给回调函数的数据

定义函数void jit_tasklet_fn(unsigned long arg)

{

//中断的底半部 执行该函数的时候,已经出中断了

printk(“in jit_tasklet_fn jiffies=%ld\n”,jiffies);

}

在需要调度的地方调用以下函数

tasklet_schedule(&tlet);

一般在中断函数当中调度在不晚于下一个时钟滴答之前执行

【tasklet 和定期器的区别】

\1. 执行时间

定时器的执行:时间是确定的

tasklet :不确定的

2.tasklet 执行耗时的操作的

18.、内核函数mmap的实现原理,机制?

mmap函数实现把一个文件映射到一个内存区域,从而我们可以像读写内存一样读写文件,他比单纯 调用read/write也要快上许多。在某些时候我们可以把内存的内容拷贝到一个文件中实现内存备份, 当然,也可以把文件的内容映射到内存来恢复某些服务。另外,mmap实现共享内存也是其主要应用之 一,mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。

19.驱动里面为什么要有并发、互斥的控制?如何实现?讲个例子?

并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共 享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(raceconditions)。

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元 在访问共享资源的时候,其他的执行单元都被禁止访问。

访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。

20.tasklet和workqueue区别?

tasklet运行于中断上下文,不允许阻塞 、休眠,而workqueue运行与进程上下文,可以休眠和阻塞

如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列,它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠,这意味着你在你需要获得大量的内存时,在你需要获取信号量时,在你需要执行阻塞式的IO操作时,它都会非常有用,如果你不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet吧

21.spinlock自旋锁是如何实现的?

自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区 中。这点可以应用在多处理机器、或运行在单处理器上的抢占式内核中需要的锁定服务

22.信号量简介

这里也介绍下信号量的概念,因为它的用法和自旋锁有相似的地方。 Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推 入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释 放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。

23.信号量和自旋锁的区别

信号量一般使用在等待时间较长,可以进入睡眠的场景

自旋锁不能进入睡眠,也就是说里面不能出现阻塞的函数,自旋锁如果得不到锁的话会一直在等待,不会进入睡眠,所以会占用较高的CPU,所以一般适用于等待时间比较短的场景

中断中可以有自旋锁,不能出现信号量

24.linux defconfig、 .config 、kconfig 与makefile?

deconfig 文件:

一般由平台厂商提供,内核编译用做.config的参考,注意:如果,缺少该文件,无法进行编译。文件位于:\kernel…\arch\arm\configs\xxx_defconfig

kconfig(菜单)

分布在各目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源

文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出配置菜

单,用户配置完后保存到.config(在顶层目录下生成)中。

makefile

分布在各个目录下,用于集成编译。

.config 文件(特色菜)

在内核编译时,主Makefile调用这个.config,用于或者用于的内核编译配置。当我们在内核源码目录 下输入make menuconfig时,在出现的菜单界面中选择一项时,它会自动更新.config相应项的值。如 果我们没有选择,则会在.config问下插入一行注释

25.platform设备模型的设计目的与匹配细节

设计目的

platform平台设备驱动是基于设备总线驱动模型的。Linux设备模型是对系统设备组织架构进行抽 象的一个数据结构,旨在为设备驱动进行分层、分类、组织,更加有益于移植。降低设备多样性带来的Linux驱动开发的 复杂度,以及设备热拔插处理、电源管理等。

平台设备匹配的依据是:

1)根据平台设备结构和平台驱动结构中的name成员进行匹配。-- 一个设备对应一个驱动

2)根据平台设备结构中的name和平台驱动中的id_table 成员中的列表进行匹配 --一个驱动对应多个 设备

匹配过程:

以name匹配过程:

1)先安装驱动,再安装设备

安装驱动,把驱动结构存放在内核驱动链表上,当安装一个设备时,内核触发一个动作,把设备结构 中的name取出来和驱动链表上的每一个结构中的name进行字符串比较,如果相同,则调用驱动中的探 测函数

2)先安装设备,再安装驱动

安装设备,把设备结构存放在内核设备链表上,当安装一个驱动时,内核触发一个动作,把驱动结构

中的name取出来和设备链表上的每一个结构中的name进行字符串比较,如果相同,则调用驱动中的探

测函数。

当探测函数调用时,传递一个实际参数,这个参数就是和它匹配的平台设备结构的地址。这样就实现

两个层关联

26.介绍一下Linux设备树

DTS即DeviceTree Source 设备树源码,是一种描述硬件的数据结构,以树状节点的方式描述一个设备的各种硬件信息细节:CPU、GPIO、时钟、中断、内存等,形成类似文本文件dts,直接透过它传递给Linux,使得驱动程序与硬件分离,只需要修改dts文件,便能实现需求。设备树易于扩展,硬件有变动时不需要重新编译内核或驱动程序,只需要提供不一样的dtb文件。

27.U-boot的启动流程

第一阶段的功能:

  • 硬件设备初始化;
  • 加载u-boot第二阶段的代码到RAM空间;
  • 设置好栈;
  • 跳转到u-boot第二阶段代码的入口处;

第二阶段的功能:

  • 初始化本阶段使用的硬件设备;
  • 检测系统内存映射;
  • 将Linux内核从Flash读取到RAM中;
  • 为Linux内核设置启动参数;
  • 调用Linux内核。

28.简述linux系统启动过程

Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段:

  1. 内核的引导。
  2. 运行 init。
  3. 系统初始化。
  4. 建立终端 。
  5. 用户登录系统。

29.嵌入式设备,为加快启动速度,可以做哪些方面的优化?

linux默认的安装内核相当庞大,为了保证系统的兼容性和灵活性,支持热插拔操作,内核启动时要进行大量的硬件检测和初始化工作,而嵌入式的硬件都是固定的,只需要选择需要的硬件驱动就可以,不需要全部的硬件驱动都检测;因此可以进行适当的裁剪内核达到缩小启动linux系统的目的;同时可以统计驱动模块的耗时时间,对耗时较长的模块驱动加以分析,优化

30.中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?

中断是CPU处于被动状态下来接受设备的信号,而轮询是CPU主动去查询该设备是否有请求。凡事都是 两面性,所以,看效率不能简单的说那个效率高。

如果是请求设备是一个频繁请求cpu的设备,或者有 大量数据请求的网络设备,那么轮询的效率是比中断高

如果是一般设备,并且该设备请求cpu的频率 比较低,则用中断效率要高一些

主要是看请求频率。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐