Linux 驱动常见面试及答案

https://blog.csdn.net/yaolixiao001/article/details/80092350

简单介绍:

 

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

 

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(x,ptr):在内核中被调用,获取用户空间指定地址的数值并保存到内核变量x中。

 

B.put_user(x,ptr):在内核中被调用,将内核空间的变量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.netlink:netlink 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_operations中mmap函数,我们还可以重写其他的方法如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 函数的管理机制,实现数据包在三层以上的过滤、地址转换(SNAT、DNAT)、基于协议的连接跟踪。我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。

 

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

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

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

NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_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内核启动流程 

https://www.linuxidc.com/Linux/2014-10/108034.htm

https://blog.csdn.net/ysh1042436059/article/details/86422072

 

  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 | MODE_SVC@ 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 

 

1. start_kernel()函数分析

  下面对start_kernel()函数及其相关函数进行分析。 

1.1 lock_kernel() 

 

 1 /* Getting the big kernel lock. 
 2 * This cannot happen asynchronously, 
 3 * so we only need to worry about other 
 4 * CPU's. 
 5 */ 
 6 extern __inline__ void lock_kernel(void) 
 7 { 
 8     if (!++current->lock_depth) 
 9     spin_lock(&kernel_flag); 
10 } 

 

  kernel_flag 是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。

  只有获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对 lock_kernelunlock_kernel函数里至多可以有一个程序占用CPU

  进程的lock_depth成员初始化为-1,在 kerenl/fork.c文件中设置。在它小于0 (恒为 -1),进程不拥有内核锁;当大于或等于0时,进程得到内核锁。 

1.2 setup_arch() 

  setup_arch()函数做体系相关的初始化工作,函数的定义在arch/arm/kernel/setup.c文件中,主要涉及下列主要函数及代码。 

  setup_processor() 
  该函数主要通过 

for (list = &__proc_info_begin; list < &__proc_info_end ; list++) 
if ((processor_id & list->cpu_mask) == list->cpu_val) 
break; 

  这样一个循环来在.proc.info段中寻找匹配的processor_idprocessor_idhead_armv.S文件中设置。 

1.2.2 setup_architecture(machine_arch_type) 

  该函数获得体系结构的信息,返回mach-xxx/arch.c 文件中定义的machine结构体的指针,包含以下内容 

 

MACHINE_START (xxx, “xxx”) 
MAINTAINER ("xxx" 
BOOT_MEM (xxx, xxx, xxx) 
FIXUP (xxx) 
MAPIO (xxx) 
INITIRQ (xxx) 
MACHINE_END 

 

1.2.3内存设置代码 

 

if (meminfo.nr_banks == 0) 
{ 
    meminfo.nr_banks = 1; 
    meminfo.bank[0].start = PHYS_OFFSET; 
    meminfo.bank[0].size = MEM_SIZE; 
} 

  meminfo结构表明内存情况,是对物理内存结构meminfo的默认初始化。

  nr_banks指定内存块的数量,bank指定每块内存的范围,PHYS _OFFSET指定某块内存块的开始地址,MEM_SIZE指定某块内存块长度。 PHYS _OFFSETMEM_SIZE都定义在include/asm-armnommu/arch-XXX/memory.h文件中,其中 PHYS _OFFSET是内存的开始地址,MEM_SIZE就是内存的结束地址。

  这个结构在接下来内存的初始化代码中起重要作用。 

1.2.4 内核内存空间管理 

init_mm.start_code = (unsigned long) &_text; 内核代码段开始 
init_mm.end_code = (unsigned long) &_etext; 内核代码段结束 
init_mm.end_data = (unsigned long) &_edata; 内核数据段开始 
init_mm.brk = (unsigned long) &_end; 内核数据段结束 

 

  每一个任务都有一个mm_struct结构管理其内存空间,init_mm 是内核的mm_struct

  其中设置成员变量* mmap指向自己, 意味着内核只有一个内存管理结构,设置 pgd=swapper_pg_dirswapper_pg_dir是内核的页目录,ARM体系结构的内核页目录大小定义为16kinit_mm定义了整个内核的内存空间,内核线程属于内核代码,同样使用内核空间,其访问内存空间的权限与内核一样。 

1.2.5 内存结构初始化

  bootmem_init (&meminfo)函数根据meminfo进行内存结构初始化。

  bootmem_init(&meminfo)函数中调用reserve_node_zero(bootmap_pfn, bootmap_pages) 函数,这个函数的作用是保留一部分内存使之不能被动态分配。

  这些内存块包括:

reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext); /*内核所占用地址空间*/ 
reserve_bootmem_node(pgdat, bootmap_pfn<<PAGE_SHIFT, bootmap_pages<<PAGE_SHIFT) /*bootmem结构所占用地址空间*/

1.2.6 paging_init(&meminfo, mdesc) 

  创建内核页表,映射所有物理内存和IO空间,对于不同的处理器,该函数差别比较大。

  下面简单描述一下ARM体系结构的存储系统及MMU相关的概念。 
  在ARM存储系统中,使用内存管理单元(MMU)实现虚拟地址到实际物理地址的映射。

  利用MMU,可把SDRAM的地址完全映射到0x0起始的一片连续地址空间,而把原来占据这片空间的FLASH或者ROM映射到其他不相冲突的存储空间位置。

  例如,FLASH的地址从0x0000 00000x00FFFFFF,而SDRAM的地址范围是0x3000 00000x3lFFFFFF,则可把SDRAM地址映射为0x0000 00000xlFFFFFF,而FLASH的地址可以映射到0x9000 00000x90FFFFFF(此处地址空间为空闲,未被占用)。映射完成后,如果处理器发生异常,假设依然为IRQ中断,PC指针指向0xl8处的地址,而这个时候PC实际上是从位于物理地址的0x3000 0018处读取指令。

  通过MMU的映射,则可实现程序完全运行在SDRAM之中。在实际的应用中.可能会把两片不连续的物理地址空间分配给SDRAM。而在操作系统中,习惯于把SDRAM的空间连续起来,方便内存管理,且应用程序申请大块的内存时,操作系统内核也可方便地分配。通过MMU可实现不连续的物理地址空间映射为连续的虚拟地址空间。操作系统内核或者一些比较关键的代码,一般是不希望被用户应用程序访问。通过MMU可以控制地址空间的访问权限,从而保护这些代码不被破坏。 

  MMU的实现过程,实际上就是一个查表映射的过程。建立页表是实现MMU功能不可缺少的一步。页表位于系统的内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度即是一个字的长度(ARM中,一个字的长度被定义为4Bytes)。页表项除完成虚拟地址到物理地址的映射功能之外,还定义了访问权限和缓冲特性等。 
  MMU的映射分为两种,一级页表的变换和二级页表变换。两者的不同之处就是实现的变换地址空间大小不同。 
  一级页表变换支持1 M大小的存储空间的映射,而二级可以支持64 kB4 kB1 kB大小地址空间的映射。

动态表(页表)的大小=表项数*每个表项所需的位数,即为整个内存空间建立索引表时,需要多大空间存放索引表本身。 
表项数=虚拟地址空间/每页大小 
每个表项所需的位数=Log(实际页表数)+适当控制位数 
实际页表数 =物理地址空间/每页大小

 

linux内核调度器 调度原理(2.6.24笔记整理)

 

https://blog.csdn.net/janneoevans/article/details/8125106

https://blog.csdn.net/ysh1042436059/article/details/86421792

 

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中)独立分开存放,因此好处是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值