前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LINUX一些面试问题集合

LINUX一些面试问题集合

作者头像
心跳包
发布2020-08-31 11:05:24
1.1K0
发布2020-08-31 11:05:24
举报

题一: 简述memcpy和strcpy的区别?

题二:信号量与互斥锁的区别?

题三:简述程序编译的过程?

题一答案:

(1)复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

(2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符串的结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

(3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时一般用memcpy。

题二答案:

(1)互斥量用于线程的互斥,信号量用于线程的同步。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已 经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

(2)互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量时,也可以完成一个资源的互斥访问。

(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

题三答案:

预处理:预处理命令以符号“#”开头。预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。C语言的预处理主要有三个方面的内容:

1. 宏定义。

2. 文件包含。

3. 条件编译。

编译: 将预处理输出的.i文件转化成.s汇编文件。

这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言。

汇编:将汇编文件翻译成机器语言,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。

链接:将引用的其他o文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。

题一,堆和栈的区别是?

(1)存储内容不同

栈:在函数调用时,栈中存放的是函数中(最底下是函数调用后的下一条指令)的各个参数(局部变量)。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员分配。

(2)管理方式上不同

栈:由系统自动分配并释放空间。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间,当对应的生存周期结束后栈空间被自动释放。

堆:需要程序员指定大小手动申请和手动释放,在C语言中使用malloc函数申请,使用free函数释放。

(3)空间大小不同

栈:获取空间较小。在Windows下一般大小是1M或2M,当剩余栈空间不足时,分配失败overflow。

堆:获得空间根据系统的有效虚拟内存有关,比较灵活、大。

(4)能否产生碎片不同

栈:不会产生碎片,空间连续。

堆:采用的是链表的存储方式,会产生碎片。

(5)生长方向不同

栈: 向低地址扩展的数据结构,是一块连续的内存区域。

堆: 向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表空闲内存地址来存储的,自然不连续,而链表的遍历方向是由低地址向高地址。

(6)分配方式不同

栈:有2种分配方式:静态分配和动态分配,静态由编译器完成,例如局部变量;动态由malloc函数实现,由编译器进行释放。

堆: 都是动态分配的,没有静态分配的堆。

(7)分配效率不同

栈:由系统自动分配,速度较快。但程序员无法控制。

堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 题二,Volatile与Register的区别是?

a.volatile

volatile是易变的,不稳定的意思,volatile是关键字,是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程等,遇到这个关键字声明的变量,编译器对访问该变量的代码不在进行优化,从而可以提供对特殊地址的稳定访问。

1、并行设备的硬件寄存器(如:状态寄存器);

2、一个中断服务子程序中会访问到的非自动变量();

3、多线程应用中被几个任务共享的变量;

上面提到了非自动变量,这里进一步对几种变量做一番解释:

自动变量:是在函数内部定义和使用的变量,它是局部变量。

非自动变量:有两种,一种是全局变量,一种是静态变量。

b. register

这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。你想想,一个CPU 的寄存器也就那么几个或几十个,你要是定义了很多很多register 变量,它累死也可能不能全部把这些变量放入寄存器吧。

sizeof函数和strlen函数的区别

一、sizeof sizeof(...)是运算符,在头文件中typedef为unsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。 它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。 由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。 具体而言,当参数分别如下时,sizeof返回的值表示的含义如下: 数组——编译时分配的数组空间大小; 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4); 类型——该类型所占的空间大小; 对象——对象的实际占用空间大小; 函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。 ************** 二、strlen strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。 它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL。

变量声明和变量定义

变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。

变量声明:用于向程序表明变量的类型和名字。

定义也是声明,extern声明不是定义

定义也是声明:当定义变量时我们声明了它的类型和名字。 extern声明不是定义:通过使用extern关键字声明变量名而不定义它。 [注意] 变量在使用前就要被定义或者声明。 在一个程序中,变量只能定义一次,却可以声明多次。 定义分配存储空间,而声明不会。

Linux进程调度原理

    Linux进程调度的目标

    1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;

    2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;

    3.保证公平和避免饥渴;

    4.SMP调度:调度程序必须支持多处理系统;

    5.软实时调度:系统必须有效的调用实时进程,但不保证一定满足其要求;

linux系统调用、库函数和内核函数关系与区别

系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用库函数来对文件进行操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,都必然会引起系统调用。也就是说,库函数对文件的操作实际上是通过系统调用来实现的。例如C库函数fwrite()就是通过write()系统调用来实现的。 这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为,读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。 系统调用与系统命令:系统命令相对API更高一层,每个系统命令都是一个可执行程序,比如常用的系统命令ls、hostname等,比如strace ls就会发现他们调用了诸如open(),brk(),fstat(),ioctl()等系统调用。 系统调用是用户进程进入内核的接口层,它本身并非内核函数,但他是由内核函数实现的,进入系统内核后,不同的系统调用会找到各自对应的内核函数,这写内核函数被称为系统调用的“服务例程”。也可以说系统调用是服务例程的封装例程。

数组和指针的区别与联系

一、概念

数组:数组是用于储存多个相同类型数据的集合。 指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。 二、赋值、存储方式、求sizeof、初始化等

1.赋值

同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝 2.存储方式

数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。 数组的存储空间,不是在静态区就是在栈上。 指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。 指针:由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。 3.求sizeof

数组: 数组所占存储空间的内存:sizeof(数组名) 数组的大小:sizeof(数组名)/sizeof(数据类型) 指针: 在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。 1)指针数组:它实际上是一个数组,数组的每个元素存放的是一个指针类型的元素。

(2)数组指针:它实际上是一个指针,该指针指向一个数组。

Linux的僵尸进程产生原因及解决方法

1. 产生原因:

在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程。通过ps命令查看其带有defunct的标志。僵尸进程是一个早已死亡的进程,但在进程表 (processs table)中仍占了一个位置(slot)。

但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看看有没有哪个 进程是刚刚结束的这个进程的子进程,如果是的话,就由Init进程来接管他,成为他的父进程,从而保证每个进程都会有一个父进程。而Init进程会自动 wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。

2. 原理分析:

每个Unix进程在进程表里都有一个进入点(entry),核心进程执 行该进程时使用到的一切信息都存储在进入点。当用 ps 命令察看系统中的进程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个 进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。

子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。那么会不会因为父进程太忙来不及 wait 子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢?不会。因为UNIX提供了一种机制可以保证,只要父进程想知道子进程结束时的 状态信息,就可以得到。这种机制就是:当子进程走完了自己的生命周期后,它会执行exit()系统调用,内核释放该进程所有的资源,包括打开的文件,占用 的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出码exit code,退出状态the terminationstatus of the process,运行时间the amount of CPU time taken by the process等),这些数据会一直保留到系统将它传递给它的父进程为止,直到父进程通过wait / waitpid来取时才释放。

3.解决方法:

(1) 父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。

执行wait()或waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,此时系统会立即删除该进入点。在这种情形下就不会产生defunct进程。

(2) 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler。在子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。

(3) 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCLD, SIG_IGN)或signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号

(4)fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。

什么是内存泄漏,如何进行检测内存泄漏

内存泄漏:由于疏忽或者错误造成程序未能释放已经不再使用的情况,内存泄漏并不是指内存在物理上的错误消失,而是程序分配某段内存后,由于设计错误,丢失了对这段内存的控制,因而造成了内存浪费。

如何进行内存泄漏的检测 检测内存泄漏的方法: 1.使用调试器和C运行库(CRT)调试堆函数。 具体函数:_CrtSetDbgFlag()可以在作用于结束位置,自动调用_CrtDumpMemoryLeaks()。

2、如何检测内存泄露

第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。

第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。

第三:Boost 中的smart pointer。

第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。

IIC总线为什么可以多个设备

IIC总线 一般串行数据通讯都有时钟和数据之分,有异步和同步之别. 有单线,双线和三线等.

I2C肯定是2线的(不算地线).

I2C协议确实很科学,比3/4线的SPI要好,当然线多通讯速率相对就快了.

I2C的原则是:

在SCL=1(高电平)时,SDA千万别忽悠!!!

否则,SDA下跳则"判罚"为"起始信号S",SDA上跳则"判罚"为"停止信号P".

在SCL=0(低电平)时,SDA随便忽悠!!!(可别忽悠过火到SCL跳高)

每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.

非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.

SCL必须由主机发送,否则天下大乱.

首字节是"片选信号",即7位从机地址加1位方向(读写)控制.

从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.

其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)

读写是站在主机的立场上定义的.

"读"是主机接收从机数据,"写"是主机发送数据给从机.

重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,

所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,而I2C不行.

在硬件I2C模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,

根据这个状态码可以很容易知道现在在什么状态和什么出错信息.

7位I2C总线可以挂接127个不同地址的I2C设备,0号"设备"作为群呼地址.

10位I2C总线可以挂接更多的10位I2C设备.

总之,只要掌握I2C的忽悠记,一般很容易掌控... 第一个字节(为slave address)由7位地址和一位R/W读写位组成的,这字节是个器件地址。 首先,你要知道:常用IIC接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。 如格式如下: D7 D6 D5 D4 D3 D2 D1 D0 1-器件类型由:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。

2-用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。 所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了。

3-最低一位就是R/W位。这位不用我多说了。 在现代电子系统中,有为数众多的IC需要进行相互之间以及与外界的通信。为了提供硬件的效率和简化电路的设计,PHILIPS开发了一种用于内部IC控制的简单的双向两线串行总线I2C。I2C总线支持任何一种IC制造工艺,并且PHILIPS和其他厂商提供了种类非常丰富的I2C兼容芯片。作为一个专利的控制总线,I2C已经成为世界性的工业标准。

每个器件都有一个唯一的地址,而且可以是单接收的器件(例如:LCD驱动器)或者可以接收也可以发送的器件(例如:存储器)。发送器或接收器可以在主模式或从模式下操作,这取决于芯片是否必须启动数据的传输还是仅仅被寻址。I2C是一个多主总线,即它可以由多个连接的器件控制。 基本的I2C总线规范于20年前发布,其数据传输速率最高为100Kbits/s,采用7位寻址。但是由于数据传输速率和应用功能的迅速增加,I2C总线也增强为快速模式(400Kbits/s)和10位寻址以满足更高速度和更大寻址空间的需求。 I2C总线始终和先进技术保持同步,但仍然保持其向下兼容性。并且最近还增加了高速模式,其速度可达3.4Mbits/s。它使得I2C总线能够支持现有以及将来的高速串行传输应用,例如EEPROM和Flash存储器。 在现代电子系统中,有为数众多的IC需要进行相互之间以及与外界的通信。为了提供硬件的效率和简化电路的设计,PHILIPS开发了一种用于内部IC控制的简单的双向两线串行总线I2C。I2C总线支持任何一种IC制造工艺,并且PHILIPS和其他厂商提供了种类非常丰富的I2C兼容芯片。作为一个专利的控制总线,I2C已经成为世界性的工业标准。

每个器件都有一个唯一的地址,而且可以是单接收的器件(例如:LCD驱动器)或者可以接收也可以发送的器件(例如:存储器)。发送器或接收器可以在主模式或从模式下操作,这取决于芯片是否必须启动数据的传输还是仅仅被寻址。I2C是一个多主总线,即它可以由多个连接的器件控制。 基本的I2C总线规范于20年前发布,其数据传输速率最高为100Kbits/s,采用7位寻址。但是由于数据传输速率和应用功能的迅速增加,I2C总线也增强为快速模式(400Kbits/s)和10位寻址以满足更高速度和更大寻址空间的需求。 I2C总线始终和先进技术保持同步,但仍然保持其向下兼容性。并且最近还增加了高速模式,其速度可达3.4Mbits/s。它使得I2C总线能够支持现有以及将来的高速串行传输应用,例如EEPROM和Flash存储器。

view (893×486)
view (893×486)

1. Linux中主要有哪几种内核锁?

Linux的同步机制从2.0到2.6以来不断发展完善。从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡;伴随着从非抢占内核到抢占内核的过度。Linux的锁机制越来越有效,也越来越复杂。

自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图请求一个已被争用已经被持有)的自旋锁,那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。

信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,另外一些同步机制,包括大内核锁、读写锁、大读者锁、RCU (Read-Copy Update,顾名思义就是读-拷贝修改),和顺序锁。

2. Linux中的用户模式和内核模式是什么含意?

MS-DOS等操作系统在单一的CPU模式下运行,但是一些类Unix的操作系统则使用了双模式,可以有效地实现时间共享。在Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内核本身处于内核模式以外,所有的用户进程都运行在用户模式之中。

内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果用户模式的进程要享有此特权,它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。另外,用户模式的代码允许发生缺页,而内核模式的代码则不允许。

在2.4和更早的内核中,仅仅用户模式的进程可以被上下文切换出局,由其他进程抢占。除非发生以下两种情况,否则内核模式代码可以一直独占CPU:

(1) 它自愿放弃CPU;

(2) 发生中断或异常。

2.6内核引入了内核抢占,大多数内核模式的代码也可以被抢占。

3. 怎样申请大块内核内存?

在Linux内核环境下,申请大块内存的成功率随着系统运行时间的增加而减少,虽然可以通过vmalloc系列调用申请物理不连续但虚拟地址连续的内存,但毕竟其使用效率不高且在32位系统上vmalloc的内存地址空间有限。所以,一般的建议是在系统启动阶段申请大块内存,但是其成功的概率也只是比较高而已,而不是100%。如果程序真的比较在意这个申请的成功与否,只能退用“启动内存”Boot Memory)。下面就是申请并导出启动内存的一段示例代码:

代码语言:javascript
复制
void* x_bootmem = NULL;
EXPORT_SYMBOL(x_bootmem);
unsigned long x_bootmem_size = 0;
EXPORT_SYMBOL(x_bootmem_size);
static int __init x_bootmem_setup(char *str)
{
x_bootmem_size = memparse(str, &str);
x_bootmem = alloc_bootmem(x_bootmem_size);
printk("Reserved %lu bytes from %p for x\n", x_bootmem_size, x_bootmem);
return 1;
}
__setup("x-bootmem=", x_bootmem_setup);
  1. 可见其应用还是比较简单的,不过利弊总是共生的,它不可避免也有其自身的限制:

内存申请代码只能连接进内核,不能在模块中使用。被申请的内存不会被页分配器和slab分配器所使用和统计,也就是说它处于系统的可见内存之外,即使在将来的某个地方你释放了它。一般用户只会申请一大块内存,如果需要在其上实现复杂的内存管理则需要自己实现。在不允许内存分配失败的场合,通过启动内存预留内存空间将是我们唯一的选择。

4. 用户进程间通信主要哪几种方式?

1)管道Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信

2)命名管道named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

3)信号Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。

4)消息Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺

6)信号量semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

7)套接字Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

5. 通过伙伴系统申请内核内存的函数有哪些?

在物理页面管理上实现了基于区的伙伴系统zone based buddy system)。对不同区的内存使用单独的伙伴系统(buddy system)管理,而且独立地监控空闲页。相应接口alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。

补充知识:

1.原理说明

* 页全局目录(Page Global Directory)

* 页中间目录(Page Middle Directory)

页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指 向一个页框。Linux中采用4KB大小的 页框作为标准的内存分配单元。

1.1.伙伴系统算法

为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个 块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连 续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。

页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。

slab分配器源于 Solaris 2.4 的 分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。

2.常用内存分配函数

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

__get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函 数,从代码分析,alloc_pages函数会分配长度为1<

struct kmem_cache *kmem_cache_create(const char *name, size_t size

void (*ctor)(void*, struct kmem_cache *, unsigned long),

void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)

kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从 该高速缓存区域中获取新的内存块。kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏定义,在默认的2.6.18内核版本中,该宏定义为5,于是一次最多能申请1<<5 * 4KB也就是128KB的连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分配超过128KB的内存时使内核崩溃。

void *kmalloc(size_t size, gfp_t flags)

2.4.vmalloc

前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也 可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表 示的是vmalloc分配的内存使用的地址范围。vmalloc对 一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中, 最大能一次分配1GB的空间。

2.5.dma_alloc_coherent

ma_addr_t *dma_handle, gfp_t gfp)

2.6.ioremap

ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段 物理地址映射到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存 分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。

如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内 核引导阶段来预留部分内存。

void* alloc_bootmem(unsigned long size)

2.7.2.通过内核引导参数预留顶部内存

3.几种分配函数的比较

__get_free_pages直接对页框进行操作4MB适用于分配较大量的连续物理内存

kmalloc基于kmem_cache_alloc实现128KB最常见的分配方式,需要小于页框大小的内存时可以使用

dma_alloc_coherent基于__alloc_pages实现4MB适用于DMA操 作

alloc_bootmem在启动kernel时,预留一段内存,内核看不见小于物理内存大小,内存管理要求较高

6) 通过slab分配器申请内核内存的函数有?

7) Linux的内核空间和用户空间是如何划分的(以32位系统为例)?

8) vmalloc()申请的内存有什么特点?

9) 用户程序使用malloc()申请到的内存空间在什么范围?

10) 在支持并使能MMU的系统中,Linux内核和用户程序分别运行在物理地址模式还是虚拟地址模式?

11) ARM处理器是通过几级也表进行存储空间映射的? 12) Linux是通过什么组件来实现支持多种文件系通的?

虚拟文件系统。

13) Linux虚拟文件系统的关键数据结构有哪些?(至少写出四个)

struct super_block, struct inode, struct file, struct dentry; 14) 对文件或设备的操作函数保存在那个数据结构中?

struct file_operations 15) Linux中的文件包括哪些?

执行文件,普通文件,目录文件,链接文件和设备文件,管道文件。 16) 创建进程的系统调用有那些?

clone(),fork(),vfork();系统调用服务例程:sys_clone,sys_fork,sys_vfork; 17) 调用schedule()进行进程切换的方式有几种?

1.系统调用 do_fork();

2.定时中断 do_timer();

3.唤醒进程 wake_up_process

4.改变进程的调度策略 setscheduler();

5.系统调用礼让 sys_sched_yield(); 18) Linux调度程序是根据进程的动态优先级还是静态优先级来调度进程的?

Liunx调度程序是根据根据进程的动态优先级来调度进程的,但是动态优先级又是根据静态优先级根据算法计算出来的,两者是两个相关联的值。因为高优先级的进程总是比低优先级的进程先被调度,为防止多个高优先级的进程占用CPU资源,导致其他进程不能占有CPU,所以引用动态优先级概念

19) 进程调度的核心数据结构是哪个?

struct runqueue 20) 如何加载、卸载一个模块?

insmod加载,rmmod卸载 21) 模块和应用程序分别运行在什么空间?

模块运行在内核空间,应用程序运行在用户空间 22) Linux中的浮点运算由应用程序实现还是内核实现?

应用程序实现,Linux中的浮点运算是利用数学库函数实现的,库函数能够被应用程序链接后调用,不能被内核链接调用。这些运算是在应用程序中运行的,然后再把结果反馈给系统。Linux内核如果一定要进行浮点运算,需要在建立内核时选上math-emu,使用软件模拟计算浮点运算,据说这样做的代价有两个:用户在安装驱动时需要重建内核,可能会影响到其他的应用程序,使得这些应用程序在做浮点运算的时候也使用math-emu,大大的降低了效率。 23) 模块程序能否使用可链接的库函数?

模块程序运行在内核空间,不能链接库函数。 24) TLB中缓存的是什么内容?

TLB,页表缓存,当线性地址被第一次转换成物理地址的时候,将线性地址和物理地址的对应放到TLB中,用于下次访问这个线性地址时,加快转换速度。 25) Linux中有哪几种设备?

字符设备和块设备。网卡是例外,他不直接与设备文件对应,mknod系统调用用来创建设备文件。 26) 字符设备驱动程序的关键数据结构是哪个?

字符设备描述符struct cdev,cdev_alloc()用于动态的分配cdev描述符,cdev_add()用于注册一个cdev描述符,cdev包含一个struct kobject 类型的数据结构它是核心的数据结构 27) 设备驱动程序包括哪些功能函数?

open(),read(),write(),llseek(),realse(); 28) 如何唯一标识一个设备?

Linux使用一个设备编号来唯一的标示一个设备,设备编号分为:主设备号和次设备号,一般主设备号标示设备对应的驱动程序,次设备号对应设备文件指向的设备,在内核中使用dev_t来表示设备编号,一般它是32位长度,其中12位用于表示主设备号,20位用于表示次设备号,利用MKDEV(int major,int minor);用于生成一个dev_t类型的对象。 29) Linux通过什么方式实现系统调用?

靠软件中断实现的,首先,用户程序为系统调用设置参数,其中一个编号是系统调用编号,参数设置完成后,程序执行系统调用指令,x86上的软中断是有int产生的,这个指令会导致一个异常,产生一个事件,这个事件会导致处理器跳转到内核态并跳转到一个新的地址。并开始处理那里的异常处理程序,此时的异常处理就是系统调用程序。 30) Linux软中断和工作队列的作用是什么?

Linux中的软中断和工作队列是中断处理。

1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个CPU上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。

2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换。已完成不同的工作。

可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。

2、中断与异常有何区别

异常在处理的时候必须考虑与处理器的时钟同步,实际上异常也称为同步中断,在处理器执行到因编译错误而导致的错误指令时,或者在执行期间出现特殊错误,必须靠内核处理的时候,处理器就会产生一个异常;所谓中断是指外部硬件产生的一个电信号从CPU的中断引脚进入,打断CPU的运行。所谓异常是指软件运行过程中发生了一些必须作出处理的事件,CPU自动产生一个陷入来打断CPU的运行。

3、在ARM系统中,在函数调用的时候,参数是通过哪种方式传递的

当参数小于等于4的时候是通过r0-r3寄存器来进行传递的,当参数大于4的时候是通过压栈的方式进行传递。

4、简述SPI,UART,I2C三种传输方式

SPI:高速同步串行口,首发独立,可同步进行

SPI接口主要应用在EEPROM,Flash,实时时钟,A/D转化器,数字信号处理,是一种全双工同步通讯总线,该接口一般使用四条线:串行时钟线(sck),主出从入线,主入从出线,低电平有效地的从机选择线。

I2C协议:是单片机与其他芯片进行通讯的协议:

A、只要求两条总线线路,一条是串行时钟线,一条是串行数据线;

B、通过软件设定地址

C、是一个多主机总线,如果两个或更多主机同时初始化数据传送可通过冲突检测和仲裁防止数据破坏;

D、I2C总线传输的是数据的总高位

UART:主要是由一个modem(调制解调器),可以将模拟信号量转化成数字信号量。

.进程和线程的介绍

假设cpu是一个工厂,工厂中有很多车间,每个车间又有很多工人。

那么,进程就是车间,工人就是线程,一个进程可以包括多个线程,很多线程协同完成任务。

车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。有些可以容纳n个人。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。就是说进程的内存大小是有限的,有些同一时间只能一个线程使用,有些是多个线程使用。

一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。

这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。

2.操作系统的设计,因此可以归结为三点:

(1)以多进程形式,允许多个任务同时运行;

(2)以多线程形式,允许单个任务分成不同的部分运行;

(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

首先来一句概括的总论:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

3.有关linux线程的描述,​​​​ A.线程自己拥有很少的资源,但它可以使用所属进程的资源

B.由于同一进程中的多个线程具有相同的地址空间,所以它们间的同步和通信也易于实现

C.进程创建与线程创建的时空开销不相同

D.进程是资源分配的基本单位,线程是资源调度的基本单位

4.进程的三态模型

出现等待事件:运行→等待

等待结束:等待→就绪

选中:就绪→运行

落选:运行→就绪

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 变量声明和变量定义
  • Linux进程调度原理
  • linux系统调用、库函数和内核函数关系与区别
  • 数组和指针的区别与联系
  • Linux的僵尸进程产生原因及解决方法
  • 什么是内存泄漏,如何进行检测内存泄漏
    • IIC总线为什么可以多个设备
      • 补充知识:
        • 1.原理说明
        相关产品与服务
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档