抄的八股文

发布于:2024-05-17 ⋅ 阅读:(131) ⋅ 点赞:(0)
1、volatile作用

     (1)volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。(即访问该变量时直接从内存里面去获取)

      (2)防止编译器对代码进行优化。测试样例:

无volatile:对a的赋值全部都没有了。编译器可能认为a并没有使用,直接全部给优化没了

 加volatile

 2、static作用

        1.修饰局部变量,该局部变量称为静态的局部变量:静态的局部变量和“正常”的局部变量不同,不是在栈区上创建的,而是在静态区上创建的,只被初始化一次。在静态区上创建的变量,生命周期是全局的。即使函数退出,也不会被销毁。

        2.修饰全局变量/函数:改变了函数的链接属性(外部的->内部的),使得其不能跨文件使用。

3、extern "c"作用

        加上extern "C"后,用于指定函数或变量采用C语言的命名和调用约定而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

可以看到符号的命名规则确实不一样

4、c语言内存分配方式

        1、静态存储区分配:在程序编译前完成,且整个程序运行期间都存在。如全局变量,静态变量

        2、栈上分配:如果函数体内的局部变量。栈用于保存函数的局部变量,入参或者是函数的返回地址等。栈向下生长,栈空间的开辟和回收由系统自动分配,处理快,并且栈空间大小是有限制比较小

        3、堆上分配:malloc,向上生长

5、define和const都能定义常量,区别

        1、define只是在预处理时进行文本替换,不分配内存空间,它存在于程序的代码段。const会分配空间,存在于程序的数据段,可以被调用

        2、const常量有数据类型,编译器可以对其进行安全检查等。define不行

 6、typedefine和define区别

        1、原理不同:define是预处理指令,只是在预处理时进行文本替换,不进行正确性检查;typedef在编译是具有类型检查功能

        2、功能不同:typedef用来定义类型的别名,不仅包含基础数据类型,还包含自定义的类型(struct)。define不仅可以取别名,还可以定义常量等

        3、作用域不同。#define没有作用域,只要是预定义过的宏,在后面的程序中都可以用.typedef有作用域限制

void f()
{
	#define TEST 6
}
__attribute__((optimize("O2"))) int main()
{
	printf("%d\n", TEST);//out put 6
	return 0;
}
 7、头文件中定义静态变量是否可行

头文件中定义静态变量会造成资源浪费,引起程序错误等。按照编译步骤,每个头文件都会每个包含的源文件中展开,这样每个源文件都会单独存在一个静态变量。不推荐在头文件中定义任何的变量

8、交叉编译

        在一个平台上生成另一个平台上可执行的代码的过程叫做交叉编译。有些目的平台上不允许或者不能够安装我们所需要的编译器,所以我们需要再其他平台上进行编译

9、冯诺依曼结构和哈佛结构

冯诺依曼结构又称为普林斯顿结构,是一种将指令存储器和数据存储器合并在一起的结构。指令存储地址和数据存储地址指向同一个存储器的不同地址,程序指令和数据的宽度相同,节约资源

哈佛将程序指令和数据分开,它们可以有不同的数据宽度。哈佛结构还采用了独立的程序总线和数据总线,可以并行执行。这样具有更高的执行效率

10、ARM流水线技术

流水线技术通过多个功能部件并行工作来缩短程序执行时机。 流水线分为三个阶段,分别是取值、译码、执行。PC总是执行了正在取的指令,而不是正在被执行的指令。因此PC=当前正在执行的指令 + 8(假设指令占4字节)。

执行所对应的指令才是正在被执行的指令。当发生中断时,返回地址是译码的地址即 PC - 4

11、ARM几种工作模式

        1、用户模式(USR):运行在操作系统的用户态,没有权限取操作硬件资源,也不能切换到其他工作模式下。访问硬件资源或者切到其他工作模式只能通过软中断或者产生异常

        2、系统模式(SYS):系统模式是特权模式,它和用户模式共用一套寄存器。它比用户模式拥有更高的权限,可以访问所有系统资源

        3、一般中断模式(IRQ):用于处理一般的中断请求。通常在硬件产生中断信号后自动进入该模式,可以自由访问系统硬件资源

        4、快速中断模式(FIQ):用来处理对时间要求比较紧急的中断请求。主要用于高速数据传输和通道处理。FIQ有许多自己专用的寄存器(R8-R14)。这样就避免保存和恢复其他模式下的寄存器。

        5、管理模式(SVC):CPU上电后默认的模式。主要用于系统初始化。用于操作系统使用的保护模式(当中断发生时会先进入svc模式,再进IRQ模式,好像是这样的)

       6、数据访问终止模式(ABT):用于虚拟内存和存储器的包含。当程序访问非法地址、没有权限读取的内存地址会进入该模式。例如segment fault

        7、未定义模式(UND):CPU在译码阶段不能识别指令时,会进入该模式

ARM处理器总共有7运行模式。除用户模式之外的6中处理器模式称为特权模式。在特权模式下,程序可以访问所有的系统资源,也可以任意的进行处理器运行模式切换。(那感觉用户模式就是用户态,另外的6种模式运行在内核态咯)。

12、ARM寄存器以及分类

ARM体系结构相关杂记_arm r12寄存器-CSDN博客

 13、ARM指令集分类

分别Thumb指令集和ARM指令集。前者指令长度为16位,后者指令长度为32位。

ARM工作状态也会被分为两个ARM状态和Thumb状态,执行指令分别就对应了上面两种指令集

14、中断和异常的区别

中断是指外面硬件产生一个电信号从cpu的中断引脚进入,打断cpu运行。由外设触发

异常是软件运行过程中发生了一些必须处理的事件,cpu自动产生一个陷入(trap)打断自己的运行,例如系统调用,除0异常等。是由cpu内部产生的意外事件。

15、中断和DMA区别

DMA:是一种无需cpu参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。

中断是在cpu执行程序过程中,由于外设产生了中断信号,cpu必须暂停执行当前的程序,转去处理中断事件。这个过程需要cpu进行处理

16、中断为什么不能睡眠?

1、在中断上下文中,唯一能打断当前中断就只有更高优先级的中断(有些也不支持中断嵌套。日看网上说如果要支持中断嵌套就不在IRQ模式下执行中断处理程序:【ARM】-IRQ 和 FIQ 异常中断处理程序的返回_arm fiq-CSDN博客)。中断上下文中没有进程的概念,即没有task_struct和它对应,这样就没有办法唤醒它了。进程的调度都是基于task_struct而言的。

2、schedule在进行进程切换时,会保存当前进程的上下文(寄存器,进程状态,堆栈内容),以便后面恢复该进程。中断发生后,也会先保存当前进程的上下文。但是在中断执行过程中,如果进行schedule,此时寄存器的值肯定已经改变了,此时保存的就是中断上下文了(此时被中断进程的进程上下文被改变了。被中断的进程也无法恢复。那中断上下文能不能也加入进进程上下文呢??,不清楚。。)

17、下半部能不能睡眠?

中断下半部分为了软中断、tasklet、工作队列。前两个依然属于中断上下文,不能修改。工作队列是用内核线程实现的在进程上下文,可以进行休眠。

18、中断响应执行流程

cpu接收中断-->保存被打断进程的上下文信息-->跳转到中断处理程序(中断上半部)-->回到被打断的进程执行(严格讲有可能是中断下半部执行,软中断和tasklet。也有可能是回到被打断的进程执行)。中断上半部详细的流程见文章:中断上半部_linux irq顶半部是否合适函数调用-CSDN博客

19、 当一个异常出现后,ARM会执行哪些动作

详细的见文章arm系统调用过程_arm系统调用号-CSDN博客

感觉文章里面也说得差不多,后面还需要结合文章里面的代码看看

20、 怎么判断大小端

大端:低字节在高地址,高字节在低地址

小端:低字节在低地址,高字节在高地址

我的环境里面是小端。可以有如下两种方法判断大小端

21、对绝对地址0x100000赋值/跳转到地址0x100000

下图就是对地址赋值(这个地址如果无法访问,会处错)。感觉有意思的就是将这个地址转换为函数指针,利用函数调用让pc跳转的特性,跳转到该地址处(好像arm也支持直接给pc寄存器赋值)

 

22、为什么需要区分中断上半部和下半部?

中断上半部是屏蔽中断的,为了中断处理过程能够被中断打断,就将其分为了上半部和下半部。上半部处理快速处理简单的任务,剩下耗时的任务留给下半部处理。下半部能够被中断打断

23、总线设备驱动模型

自内核2.6开始,需要关注的是总线,设备和驱动三个实体,总线将设备和驱动进行绑定。在Linux内核中注册一个设备的时候,会中总线上所有驱动匹配。相反注册驱动时也会去遍历所有设备进行匹配。

Linux设备中有的是没有对应的物理总线,为了适配Linux总线模型,引入了一种虚拟总线platform总线;Linux内核在解析dts时,将device node按照一定的规则转换为platform device,然后去尝试和驱动进行匹配。详细见文章platformd device、driver注册过程-CSDN博客

24、内核镜像格式有几种?分别有什么区别?

1、u-boot经过编译之间生成的elf格式的可执行文件是u-boot,在操作系统下面是可直接执行的。但是这种格式不能用来烧录下载。能烧录下载的是u-boot.bin。它是u-boot用objcopy进行加工出来的(去掉一些无用信息)。

2、同样Linux内核也是如此。直接编译出来的elf文件是vmlinux/vmlinuz,这个是原始未经过任何加工处理的elf文件。同样需要objcopy加工支持成烧录的镜像文件Image(相当于就是精简文件大小,节省磁盘)

3、原则上Image可以直接烧录到flash上启动执行。但是开发者还是觉得太大了,所以对Image又进行了压缩,并且在压缩文件的前面加了一部分解压缩代码,构成了一个压缩格式的镜像文件zimage

4、u-boot为了启动Linux内核,还发明了一种内核格式uImage,它是通过zImage加工得到的(不知道它们又啥区别,为什么要这样做)

25、内核空间、用户空间

操作系统的核心是内核,它独立于普通的应用程序,可以访问受保护的内存,也可以访问地址硬件,权限很高。为了保证内核的安全,操作系统将虚拟地址空间分为了内核空间和用户空间。比较常见的是最高1G虚拟地址由内核使用,称为内核空间。0-3G由各个进程使用,称为用户空间

26、为什么要区分内核/用户空间

27、用户态和内核态

当进程运行于内核空间时就处于内核态,运行于用户空间时就处于用户态

在内核态下进程可以执行任何指令,运行代码的也不受限制,可以访问任何有效地址

在用户态下,进程运行于用户地址空间中,执行的代码要受到cpu的诸多检测,只能访问被允许的虚拟地址等

通过区分用户空间和内核空间,隔离了操作系统代码和应用程序代码。即使单个应用出现错误也不会影响到操作系统的稳定性

28、用户空间和内核空间的通信方式?

1、API:get_user/put_user/copy_from_user/copy_to_user

2、使用proc文件系统

3、netlink

4、mmap:将内核空间地址映射到用户空间

5、信号

29、进程、线程,区别?

1、进程是资源分配的基本单位,是程序执行时的一个实例(一个可执行文件可以被多次运行,产生多个进程)。线程是程序执行的基本单位,一个进程可能含有多个线程

2、进程有独立的地址空间,每启动一个进程,系统会为其分配地址空间,建立代码段、数据段,堆栈等。线程是和进程共享数据的,使用相同的地址空间,栈是线程私有的。cpu切换一个线程的开销比切换进程小

3、线程之间通信更方便。同一个进程下的线程共享全局变量,静态变量等。但是多线程程序一个线程死掉整个进程都会死掉。但是一个进程死掉,并不会影响到其他的进程(因为进程之间的地址空间是独立的)

30、进程状态

看了一下网上的主要是就绪、执行、阻塞三种状态。如果假设最开始创建和进程结束。就是五个状态

创建:一个应用程序从系统上启动,就是进入创建状态(其实这个我没有看过,难道进程被创建了并没有之间加入就绪队列)

就绪:进程需要的所有资源被准备就绪,被加入了就绪队列。只差被cpu调度了

运行状态:进程被cpu调度

阻塞状态:在运行期间,如果进行了阻塞操作,比如I/O或者需要等待其他资源,比如锁这些让出了cpu,进入阻塞状态

31、进程间通信方式 

管道:管道通信方式有两个缺陷。一个是半双工通信,数据只能单向流动。而是只能在具有亲缘关系的进程间使用。后面的命名管道(FIFO)就运行在无关系的进程间通信。管道通信速度慢

信号量:一种计数器,其值表示资源的个数,可以控制多个进程对共享资源的访问,主要用于同步。不能用来传递复杂消息

消息队列:由消息组成的链表,存放在内核中。有权限的进程可以向队列中添加消息,被赋予读权限的进程则从队列中读取消息。但是消息队列容量受到系统限制,并且信息复制需要额外消耗cpu时间

信号:用于通知某个进程某个事件已发生,主要作为进程间同步的手段

共享内存:映射一段能被其他进程访问的内存,共享内存是最快的IPC通信方式。但需要保证共享内存读写的同步

socket:用于和不同物理主机的进程间进行通信

32、内核同步方式

1、原子变量

2、自旋锁:原地自旋,不会休眠一直占用cpu,能保证互斥访问

3、信号量:获取锁失败的进程可以进行睡眠,主动让出cpu的使用权。

4、mutex锁:mutex更加轻便简单,在锁竞争激烈的情况下,mutex比信号量执行速度更快。另外mutex还有一个mcs机制,能够保证只有一个人在自旋等待锁被释放,而其他人进行休眠

5、读写锁:允许多个读者同时访问临界资源

6、RCU

33、内核线程、用户线程及其区别

用户线程:不需要内核支持,在用户程序中实现的线程。线程的切换由应用程序自己进行管理(也可能是库),线程的切换不涉及用户态内核态切换,操作系统感知不到用户线程的存在。但是一个线程阻塞会导致整个进程阻塞。差不多就是内核给进程分一个时间片,进程内核由自己进行管理

内核线程:由内核对线程进行创建和维护。如果进程A有3个线程,进程B有1个线程。那进程得到的时间片是B的3倍。每个内核线程都被当做单独的执行流。一个内核线程的阻塞,不会影响其他线程的运行,内核线程的切换调度需要内核支持。

34:内核线程和用户线程的优缺点

内核线程优点:有多个cpu时,一个进程的多个线程可以同时执行

缺点:线程的切换需要由内核调度,消耗资源

用户线程优点:线程调度不需要内核参与,控制简单。线程创建和消耗和管理代码比内核线程小

缺点: 进程中一个线程阻塞,会导致整个进程阻塞。

35、僵尸进程、孤儿进程,守护进程

僵尸进程:用fork创建的字进程,如果子进程退出,父进程没有用wait、waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,称为僵尸进程。僵尸进程无法被kill掉,但是可以将其父进程kill,从而使其变为孤儿进程,然后被1号进程清理

孤儿进程:父进程异常结束了,被1号进程收养

36、代码段,数据段,bss段,堆区,栈区

代码段:存放程序执行代码的区域。这部分大小在运行前就被确定,通常这块区别是只读的。也有可能存放只读的常数。如字符串

数据段:存放以初始化的全局变量和初始化为非0的静态变量。属于静态内存分配(静态内存分配在程序员编译程序时分配内存)

bss段:存放未初始化或者显示初始化为0的全局或者静态变量

堆区:用来存放被动态分配的内存,大小不固定

栈区:用来存放程序临时创建的变量,如局部变量。另外也会保持函数调用的返回地址,入参这些

37、 用户栈、内核栈

内核栈:属于操作系统空间的一个内存区域

作用:用于保存中断现场、保存函数调用的参数、函数返回地址等以及函数的局部变量

用户栈:用户进程空间的一块区域,用于保存用户空间子程序调用参数,局部变量等

38、用户栈和内核栈为什么不能用一个栈?

1、内核栈大小一般有限32K,这就限制了函数调用深度。用户栈空间比较大

2、如果只用用户栈,系统程序需要在某种保护下运行,用户栈在用户空间,不能提供保护措施

39、产生死锁原因?

多个并发的进程因争夺资源而产生相互等待的现象。产生死锁的条件:

1、互斥:某种资源一次只能允许一个进程访问,一旦某个进程开始访问后,其他进程则不能再次访问

2、占有等待:一个进程本身占有资源。当同时还有其他资源没有满足。它会一直等待其他进程释放资源,并且自己已经拿到的资源也不会释放

3、不可抢占:其他进程已经拿到的资源,除非自己释放,否则不能被其他进程获取

4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需要的资源

40、虚拟内存

一种内存管理技术,它使得应用程序认为它拥有连续可用的内存,运行编写并运行比实际系统拥有内存大的程序。每个进程都有独立的虚拟地址空间。它有如下好处:

1、扩大了地址空间,使得寻址空间比实际内存大

2、内存保护:每个进程的地址空间独立,不能互相干扰对方

3、公平内存分配。每个进程都拥有相同大小的虚拟地址空间

41、虚拟地址,逻辑地址,线性地址,物理地址

段基地址+段内偏移得到线性地址(虚拟地址),如果没有分页,此时虚拟地址就是物理地址。如果开启分页,虚拟地址经过页表检索后得到物理地址

42、内存碎片,内部碎片,外部碎片

内存碎片是多次进行内存分配造成的

内部碎片:分配给应用程序的内存,不能被完全利用而造成的碎片。应用程序占有内存区域,导致系统也无法使用。只有程序结束时,系统才能利用

外部碎片:空间太小,小到无法给任何的程序分配。这边分区域不属于任何应用程序。


网站公告

今日签到

点亮在社区的每一天
去签到