Linux 驱动常见面试及答案

简单介绍:

好久没有面试了,面试之前还是要准备一下的,尤其是对工程师来说,很纠结,不管怎么说,好好准备还是很有必要的。把网上的信息整理一下。

1. linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些?

答案:

Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G.Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为"内核空间".而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为"用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

  Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。从图中可以看出(这里无法表示图),每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

内核空间和用户空间上不同太多了,说不完,比如用户态的链表和内核链表不一样;用户态用printf,内核态用printk;用户态每个应用程序空间是虚拟的,相对独立的,内核态中却不是独立的,所以编程要非常小心。等等。


 还有用户态和内核态程序通讯的方法很多,不单单是系统调用,实际上系统调用是个不好的选择,因为需要系统调用号,这个需要统一分配。

 可以通过ioctl、sysfs、proc等来完成。

传统的实现方法:

通信方法

无法介于内核态与用户态的原因

管道(不包括命名管道)

局限于父子进程间的通信。

消息队列

在硬、软中断中无法无阻塞地接收数据。

信号量

无法介于内核态和用户态使用。

内存共享

需要信号量辅助,而信号量又无法使用。

套接字

在硬、软中断中无法无阻塞地接收数据。

别人总结的用户空间和内和空间 通信方式:具体链接如下:

https://www.cnblogs.com/dchipnau/p/5043591.html

1.使用API这是最常使用的一种方式了

A.get_user(xptr):在内核中被调用,获取用户空间指定地址的数值并保存到内核变量x中。

B.put_user(xptr):在内核中被调用,将内核空间的变量x的数值保存到到用户空间指定地址处。

C.Copy_from_user()/copy_to_user():主要应用于设备驱动读写函数中,通过系统调用触发。

2.使用proc文件系统:sysfs文件系统类似,也可以作为内核空间和用户空间交互的手段。

/proc 文件系统是一种虚拟文件系统,通过他可以作为一种linux内核空间和用户空间的。与普通文件不同,这里的虚拟文件的内容都是动态创建的。

使用/proc文件系统的方式很简单。调用create_proc_entry,返回一个proc_dir_entry指针,然后去填充这个指针指向的结构就好了,我下面的这个测试用例只是填充了其中的read_proc属性。

   下面是一个简单的测试用例,通过读虚拟出的文件可以得到内核空间传递过来的“proc ! test by qiankun!”字符串。



3.使用sysfs文件系统+kobject其实这个以前是编程实现过得,但是那天太紧张忘记了,T_T。每个在内核中注册的kobject都对应着sysfs系统中的一个目录。可以通过读取根目录下的sys目录中的文件来获得相应的信息。除了sysfs文件系统和proc文件系统之外,一些其他的虚拟文件系统也能同样达到这个效果。

4.netlinknetlink socket提供了一组类似于BSD风格的API,用于用户态和内核态的IPC。相比于其他的用户态和内核态IPC机制,netlink有几个好处:

1.使用自定义一种协议完成数据交换,不需要添加一个文件等。2.可以支持多点传送。3.支持内核先发起会话。4.异步通信,支持缓存机制。

对于用户空间,使用netlink比较简单,因为和使用socket非常的类似,下面说一下内核空间对netlink的使用,主要说一下最重要的create函数,函数原型如下:

extern struct sock *netlink_kernel_create(struct net *net,

                         int unit,unsigned int groups,
                                    void (*input)(struct sk_buff *skb),
                                           struct mutex *cb_mutex,
                                            struct module *module);

第一个参数一般传入&init_net

第二个参数指的是netlink的类型,系统定义了16个,我们如果使用的话最好自己定义。这个需和用户空间所使用的创建socket的第三个参数一致,才可以完成通信。

第四个参数指的是一个回调函数,当接受到一个消息的时候会调用这个函数。回调函数的参数为struct sk_buff类型的结构体。通过分析其结构成员可以得到传递过来的数据

第六个参数一般传入的是THIS_MODULE。指当前模块。

    

下面是对netlink的一个简单测试,将字符串“netlink test by qiankun”通过netlink输出到内核,内核再把字符串返回。Netlink类型使用的是22.

 

 

5.文件:应该说这是一种比较笨拙的做法,不过确实可以这样用。当处于内核空间的时候,直接操作文件,将想要传递的信息写入文件,然后用户空间可以读取这个文件便可以得到想要的数据了。下面是一个简单的测试程序,在内核态中,程序会向“/home/melody/str_from_kernel”文件中写入一条字符串,然后我们在用户态读取这个文件,就可以得到内核态传输过来的数据了。

 

 

6.使用mmap系统调用:可以将内核空间的地址映射到用户空间。在以前做嵌入式的时候用到几次。一方面可以在driver中修改Struct file_operations结构中的mmap函数指针来重新实现一个文件对应的映射操作。另一方面,也可以直接打开/dev/mem文件,把物理内存中的某一页映射到进程空间中的地址上。

其实,除了重写Struct file_operationsmmap函数,我们还可以重写其他的方法如ioctl等,来达到驱动内核空间和用户空间通信的方式。

 

7.信号:从内核空间向进程发送信号。这个倒是经常遇到,用户程序出现重大错误,内核发送信号杀死相应进程。

2. linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化,高端内存概念?

4G的进程地址空间被人为的分为两个部分——用户空间与内核空间。用户空间从0到3G(0xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。

虚拟地址,即逻辑地址,是指由程序产生的与段相关的偏移地址部分。物理地址 (physical address): 放在寻址总线上的地址。

地址空间大于1G的内存区域称之为高端内存



总结: 物理地址一般与CPU有关系,是给CPU指令使用的。而总线地址主要是给设备使用的,是设备中的一些内存资源

3. linux中中断的实现机制,tasklet与workqueue的区别及底层实现区别?为什么要区分上半部和下半部?
答: 
tasklet和workqueue区别? 
tasklet运行于中断上下文,不允许阻塞 、休眠,而workqueue运行与进程上下文,可以休眠和阻塞。 
为什么要区分上半部和下半部? 

中断服务程序异步执行,可能会中断其他的重要代码,包括其他中断服务程序。因此,为了避免被中断的代码延迟太长的时间,中断服务程序需要尽快运行,而且执行的时间越短越好,所以中断程序只作必须的工作,其他工作推迟到以后处理。所以Linux把中断处理切为两个部分:上半部和下半部。上半部就是中断处理程序,它需要完成的工作越少越好,执行得越快越好,一旦接收到一个中断,它就立即开始执行。像对时间敏感、与硬件相关、要求保证不被其他中断打断的任务往往放在中断处理程序中执行;而剩下的与中断有相关性但是可以延后的任务,如对数据的操作处理,则推迟一点由下半部完成。下半部分延后执行且执行期间可以相应所有中断,这样可使系统处于中断屏蔽状态的时间尽可能的短,提高了系统的响应能力。实现了程序运行快同时完成的工作量多的目标。

由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性: 
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。 
b)多个不同类型的tasklet可以并行在多个CPU上。 
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。 
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行

4.linux中断的响应执行流程?中断的申请及何时执行(何时执行中断处理函数)?

中断的响应流程:cpu接受终端->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。 

中断的申请request_irq的正确位置:应该是在第一次打开 、硬件被告知终端之前。

5.linux中的同步机制?spinlock与信号量的区别?

linux中的同步机制:自旋锁/信号量/读取所/循环缓冲区 
spinlock在得不到锁的时候,程序会循环访问锁,性能下降 

信号量在得不到锁的时候会休眠,等到可以获得锁的时候,继续执行。

6.linux中RCU原理?

RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁)。RCU适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是RCU发挥作用的最佳场景

 在RCU的实现过程中,我们主要解决以下问题:

       1,在读取过程中,另外一个线程删除了一个节点。删除线程可以把这个节点从链表中移除,但它不能直接销毁这个节点,必须等到所有的读取线程读取完成以后,才进行销毁操作。RCU中把这个过程称为宽限期(Grace period)。

       2,在读取过程中,另外一个线程插入了一个新节点,而读线程读到了这个节点,那么需要保证读到的这个节点是完整的。这里涉及到了发布-订阅机制(Publish-Subscribe Mechanism)。

       3, 保证读取链表的完整性。新增或者删除一个节点,不至于导致遍历一个链表从中间断开。但是RCU并不保证一定能读到新增的节点或者不读到要被删除的节点。

7.linux中软中断的实现原理?

构成软中断机制的核心元素包括:
1、  软中断状态寄存器soft interrupt state(irq_stat)
2、  软中断向量表(softirq_vec)
3、  软中断守护daemon

软中断的工作工程模拟了实际的中断处理过程:

1、当某一软中断时间发生后,首先需要设置对应的中断标记位,触发中断事务(raise_softirq()设置软中断状态bitmap,触发软中断事务2、然后唤醒守护线程去检测中断状态寄存器(在Linux中 软中断daemon线程函数为do_softirq()

3、如果通过查询发现某一软中断事务发生之后,那么通过软中断向量表调用软中断服务程序action()。

这就是软中断的过程,与硬件中断唯一不同 的地方是从中断标记到中断服务程序的映射过程。在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自 动完成的,但是软中断不是,其需要守护线程去实现这一过程,这也就是软件模拟的中断,故称之为软中断。
一个软中断不会去抢占另一个软中断,只有硬件中断才可以抢占软中断,所以软中断能够保证对时间的严格要求。
软中断守护daemon 是软中断机制的实现核心,其实现过程也比较简单,通过查询软中断状态irq_stat来判断事件是否发生,如果发生, 那么映 射到软中断向量表,调用执行注册的action函数就可以了。从这一点分析可以看出,软中断的服务程序的执行上下文为软中断daemon。

8.linux系统实现原子操作有哪些方法?

1. 自旋锁 spinlock。

2. 信号量。

3. SMP cpu 是 lock 指令

4. 比较特殊的可能要屏蔽中断

9.MIPS Cpu中空间地址是怎么划分的?如在uboot中如何操作设备的特定的寄存器?


1.kuseg: 0x000 0000 - 0x7FFF FFFF (低端2G)

这些是用户模式下可用的地址,即MIPS规范约定用户空间为2G。在带有MMU的机器里,这些地址都将由MMU转换。除非已经设置好MMU,否则不要使用这2G 地址。

对于没有MMU 的机器,对这2G 地址的操作由具体实现所决定。CPU使用手册会告诉你关于这方面的信息。如果你希望编写的代码具有兼容性,可以在缺少MMU 的MIPS处理器之间移植,尽量避免使用这块区域。

2.kseg0: 0x8000 0000 - 0x9FFF FFFF(512M)

只需要把最高位清零(&0x7fffffff),这些地址就被转换为物理地址,然后把它们连续地映射到物理内存的低端512M(0x0000 0000 - 0x1FFF FFFF)空间。因为这种映射是很简单的,不需要MMU转换,通常把这些地址称为“非翻译无需转换的”(Unmapped)地址区域。

对这段地址的存取都会通过高速缓存(cached)。因此在缓存(cache)未进行初始化之前,不要使用这段地址。通常在没有MMU的系统中,这段空间用于存放大多数程序和数据。对于有MMU 的系统,操作系统的内核会存放在这个区域。

3.kseg1: 0xA000 0000 - 0xBFFF FFFF(512M)

通过将最高3位清零(&0x1fffffff)的方法来把这些地址映射为相应的物理地址,与kseg0 映射的物理地址一样,都映射到物理内存的低端512M(0x0000 0000 - 0x1FFF FFFF)空间,也是“非翻译无需转换的”(Unmapped)地址区域。但要注意,kseg1不使用缓存(Uncached)。

kseg1是唯一的在系统重启时能正常工作的内存映射地址空间,这也是为什么重新启动时的入口向量是(0xBFC0 0000)会在这个区域。这个向量对应的物理地址是0x1FC0 0000。

因此你可以使用这段地址空间来访问你的初始化程序的ROM。还有大多数人把它用来访问I/O 寄存器。如果你的硬件工程师要把这段地址映射到非低端512M 空间,你应该试图说服他们。

4.kseg2: 0xC000 0000 - 0xFFFF FFFF (1G)

这段地址空间只能在核心态下使用并且要经过MMU转换。在MMU 设置好之前,不能存取这段区域。除非你在写一个真正的操作系统,一般来说你不需要使用这段地址空间。

有时,你会看到这段地址空间被分成两等分,并称之为kseg2和kseg3,要着重指出的是其中的低半部分(kseg2)对于运行在监管者模式可用

由于kseg0和kseg1用于操作系统分配外设I/O地址,加上kseg2的1G空间,故MIPS规范约定内核空间为2G。

10. linux中netfilter的实现机制?是如何实现对特定数据包进行处理(如过滤,NAT之类的)及HOOK点的注册?

Linux netfilter就是借助一整套的 hook 函数的管理机制,实现数据包在三层以上的过滤、地址转换(SNATDNAT)、基于协议的连接跟踪。我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。

只不过二层的filter实现与三层及三层也上的filter实现有所不同。其中二层的filter与应用层程序ebtables结合使用,而三层及以上的filter结合iptables使用。但是二层filter与三层filter使用的都是统一的hook机制。

 

下面我们就在分析三层及三层以上的netfilter之前,分析一下整体的hook机制及工作流程

 

 

linux抽象出整体的hook架构,通过在以下几个数据流经点添加hook机制,为实现netfilter提供基础框架:

NF_IP_PRE_ROUTINGNF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUTNF_IP_POST_ROUTING

这五个点在数据的流经方向如下图:


详细信息

11. linux中系统调用过程?如:应用程序中read()在linux中执行过程即从用户空间到内核空间?

前文已经提到了Linux下的系统调用是通过0x80实现的,但是我们知道操作系统会有多个系统调用(Linux下有319个系统调用),而对于同一个中断号是如何处理多个不同的系统调用的?最简单的方式是对于不同的系统调用采用不同的中断号,但是中断号明显是一种稀缺资源,Linux显然不会这么做;还有一个问题就是系统调用是需要提供参数,并且具有返回值的,这些参数又是怎么传递的?也就是说,对于系统调用我们要搞清楚两点:

        1. 系统调用的函数名称转换。

        2. 系统调用的参数传递。

详细信息

12.ARM下U-boot给Kernel传参数

我们可能都知道:U-boot会给Linux Kernel传递很多参数,如:串口波特率,RAM Size,videofb、MAC Address等,而且Linux kernel也会读取和处理这些参数。

两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;

Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。

大家也知道在ARM架构上,u-boot向Linux内核传递参数利用了R0,R1和R2三个寄存器,并采用如下约定:

R0

暂时不用,缺省放0

R1

机器号,标识计算机系统的型号,

内核支持的所有使用ARM处理器的设备ID号定义在arch/arm/tools/mach-types文件中,

编译内核过程中会被转换为一个头文件include/asm-arm/mach-types.h供其他文件包含使用。

R2

R2寄存器传递的是一个地址,也就是指针的概念,这个指针指向一个TAG区域.

UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,

如 command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。

下面就一下AM335X SDK6.0的Code进行分析

详细地址

13 . linux内核的启动过程(源代码级)?

Linux内核启动流程 

  arch/arm/kernel/head-armv.S 

  该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码

  主要作用是检查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数。在执行前,处理器应满足以下状态: 

r0 - should be 0 
r1 - unique architecture number 
MMU - off 
I-cache - on or off 
D-cache – off 

 1 /* 部分源代码分析 */ 
 2 /* 内核入口点 */ 
 3 ENTRY(stext) 
 4 /* 程序状态,禁止FIQ、IRQ,设定SVC模式 */ 
 5 mov r0, #F_BIT | I_BIT | [email protected] make sure svc mode 
 6 /* 置当前程序状态寄存器 */ 
 7 msr cpsr_c, r0 @ and all irqs disabled 
 8 /* 判断CPU类型,查找运行的CPU ID值与Linux编译支持的ID值是否支持 */ 
 9 bl __lookup_processor_type 
10 /* 跳到__error */ 
11 teq r10, #0 @ invalid processor? 
12 moveq r0, #'p' @ yes, error 'p' 
13 beq __error 
14 /* 判断体系类型,查看R1寄存器的Architecture Type值是否支持 */ 
15 bl __lookup_architecture_type 
16 /* 不支持,跳到出错 */ 
17 teq r7, #0 @ invalid architecture? 
18 moveq r0, #'a' @ yes, error 'a' 
19 beq __error 
20 /* 创建核心页表 */ 
21 bl __create_page_tables 
22 adr lr, __ret @ return address 
23 add pc, r10, #12 @ initialise processor 
24 /* 跳转到start_kernel函数 */ 
25 b start_kernel 

start_kernel()函数分析

14. linux调度原理?

详细内容

15. linux网络子系统?




16. 判断大端小端模式

int checkEndion( void )
{
    union check
    {
        int i;
        char ch;
    }c;
    c.i = 1;
    return (c.ch ==1);
}

变量 i 占 4 个字节,但只有一个字节的值为 1,另外三个字节的值都为 0。如果取出低地址上的值为 0,毫无疑问,这是大端模式;如果取出低地址上的值为 1,毫无疑问,这是小端模式。

大小端模式转换

#define ___swab32(x)

{

            __u32 __x = (x);

            ((__u32)(

                        (((__u32)(__x) & (__u32)0x000000ffUL) << 24) |

                        (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) |

                        (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) |

                        (((__u32)(__x) & (__u32)0xff000000UL) >> 24) ));

}


为什么会产生僵尸进程?

如果子进程结束之后,父进程继续运行但没有调用wait/waitpid,子进程将会变成僵尸进程。僵尸进程的存在是因为子进程认为父进程会需要子进程的信息。

如何清理僵尸进程?

答:在main函数内部给SIGCHLD注册一个信号处理函数handler;定义信号处理函数void handler(waitpid(-1,NULL, WNOHANG)> 0), 调用waitpid处理僵尸进程。

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

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

18. 

linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些。

内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程的代码和数据。

1.使用API .2.使用proc文件系统3.使用sysfs文件系统+kobject4.netlink5.文件. 6.使用mmap系统调用:可以将内核空间

的地址映射到用户空间。7.信号:从内核空间向进程发送信号。

19. 

块设备驱动系统架构:





NAND驱动的probe流程

probe 函数就会开始与NAND 芯片进行交互了,它要做的事情主要包括这几个方面:读取NAND 芯片的ID ,然后查表得到这片NAND 芯片的如厂商,page size ,erase size 以及chip size 等信息,接着,根据struct nand_chip 中options 的值的不同,或者在NAND 芯片中的特定位置查找bad block table ,或者scan 整个NAND 芯片,并在内存中建立bad block table 。说起来复杂,但其实所有的这些动作,都可以在MTD 提供的一个叫做nand_scan 的函数中完成。


详细信息

ARM 架构 

哈佛结构&冯诺依曼结构

  程序和数据都放在内存中,且不彼此分离的结构称为冯诺依曼结构。譬如Intel的CPU均采用冯诺依曼结构。
  程序和数据分开独立放在不同的内存块中,彼此完全分离的结构称为哈佛结构。譬如大部分的单片机(MCS51、ARM9等)均采用哈佛结构。 

  冯诺依曼结构中程序和数据不区分的放在一起,因此安全和稳定性是个问题,好处是处理起来简单。  哈佛结构中程序(一般放在ROM、flash中)和数据(一般放在RAM中)独立分开存放,因此好处是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)