Linux操作系统面试题总结

参考博客:

https://blog.csdn.net/xiongluo0628/article/details/81461053

https://www.cnblogs.com/nancymake/p/6516933.html

https://blog.csdn.net/cmm0401/article/details/77950122

. Linux进程地址空间 && 进程内存布局

1.1 进程空间分布概述

程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。


(Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。


(Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。

注:1.Text,
BSS, Data段在编译时已经决定了进程将占用多少VM可以通过size,知道这些信息;

      2. 正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但它可以被多个进程安全的共享。

1.2内核空间和用户空间

   Linux的虚拟地址空间范围为0~4G,Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。    内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000),另外,使用虚拟地址可以很好的保护内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。

注:多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盒中,这个沙盒就是虚拟地址空间(virtual
address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page
table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表。

进程内存空间分布如下图所示:

 通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间

 注:

1.这里是32位内核地址空间划分,64位内核地址空间划分是不同的

      2.现代的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G

的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用 。

    Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。

    相对地,应用程序则是在“用户空间”中运行。运行在用户空间的应用程序只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。

    将用户空间和内核空间置于这种非对称访问机制下有很好的安全性,能有效抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。

        内核空间在页表中拥有较高的特权级(ring2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:



  上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。

1.3进程内存布局

   Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。          

注:这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。

  几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致,这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。

     进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack

frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。

  通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page

fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack
overflow),程序收到一个段错误(segmentation
fault)。

 注:动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。

内存映射段

 在栈的下方是内存映射段,内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。

  与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:



   一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。

BBS和数据段

  在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static

int cntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。

  数据段保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static

int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。

  你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。

 你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”
  1. 进程的调度方式,调度算法总结

·
进程的三种状态:

等待态:等待某个事件的完成;

就绪态:等待系统分配处理器以便运行

运行态:占有处理器正在运行

·
三种状态的转化:

运行态到等待态:往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的;

等待态到就绪态:等待的条件已经满足,只需要分配到处理器后就能运行;

就绪态到运行态:系统按某种策略选中就绪队列中的一个进程占用处理器;

运行态到就绪态:由于外界原因是运行状态的进程让出处理器,例如有更高优先级的进程来抢占处理器,或时间片(时间片是系统分配给程序的运行时间)用完。

·
调度方式:

剥夺方式:当一个进程正在运行时,系统可基于某种原则,剥夺已分配给他的处理机,将之分配给其他进程;剥夺原则:优先权原则、短进程优先原则、时间片原则。

非剥夺方式:分配程序一旦吧处理机分配给某进程后便让他一直运行下去,知道进程完成或发生事件而阻塞时,才把处理机分配给另一个进程。

·
进程调度算法有:

先来先服务调度算法(FCFS,first
come first served):谁第一个排队,谁就先被执行,在它执行过程中,不会中断它;

短作业优先调度算法(SJF,shortest
job first):对预计执行时间短的进程有限分配处理机,通常后来的短进程不会抢先正在执行的进程;对长进程非常不利,可能长时间得不到执行。

最高响应比优先法(HRRN,highest
response radio next):对于FCFS和SJF的平衡,FCFS方式只考虑每个作业的等待时间而未考虑执行时间的长短,而SJF只考虑了执行时间而未考虑等待时间的长短,因此两种算法在某种极端的情况下会带来某些不便。HRRN通过综合这两种情况算出响应比R,根据响应比完成调度。优点:长作业也有机会投入运行,缺点:每次调度前要计算响应比。

时间片轮转法(RR,Round-Robin):采用剥夺方式,每个进程被分配一个时间段,按照在队列中的顺序交替执行;不利于处理紧急作业。

多级反馈队列(multilevel
feedback queue):

UNIX使用这种调度算法;进程在进入待调度的队列等待时,首先进入优先级最高的Q1中等待;首先调度优先级高的队列中的进程。若高优先级队列中已经没有调度的进程,则调度次优先级队列的进程;同一队列中的各个进程按照时间片轮转调度;在低优先级队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(剥夺)。

多线程和多进程的区别(重点 必须从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催)!

1)进程数据是分开的:共享复杂,需要用IPC,同步简单;多线程共享进程数据:共享简单,同步复杂

2)进程创建销毁、切换复杂,速度慢 ;线程创建销毁、切换简单,速度快

3)进程占用内存多, CPU利用率低;线程占用内存少,
CPU利用率高

4)进程编程简单,调试简单;线程 编程复杂,调试复杂

5)进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉

6)进程适应于多核、多机分布;线程适用于多核

线程所私有的:线程id、寄存器的值、栈、线程的优先级和调度策略、线程的私有数据、信号屏蔽字、errno变量、

进程和线程的区别?(考察!)

(1)定义:

进程,它是操作系统进行资源分配的一个独立单位,它是具有一定独立功能的程序关于某个数据集合上的一次执行过程。

线程,它是进程内部的一个实体,是CPU 调度的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,它只拥有一点在运行期间必不可少的资源(如程序计数器PC,一组寄存器和栈等)。但是,它可以与同属于一个进程的其他线程 共享进程所拥有的资源。

(2)关系:

一个线程可以创建和撤销另一个线程。同一个进程内部的多个线程之间可以并发执行。相对进程而言,线程是一个更加接近于代码执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己独立的栈空间,拥有独立的执行序列。

(3)区别:

进程和线程的主要差别在于,它们是不同的操作系统资源管理方式。进程具有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。而线程,它只是一个进程内部的不同代码执行流。线程有自己的栈空间(存储局部变量),但是,线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。所以,多进程的程序要比多线程的程序更健壮。但是在不同的进程之间切换时,耗费资源较大,效率要差一些。对于一些要求同时执行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1)简而言之,一个程序至少有一个进程,一个进程至少有一个线程。

2)线程的划分尺度小于进程,使得多线程程序的并发性高。

3)另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4)线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5)从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

多线程的锁的种类有哪些?

互斥锁mutex、自旋锁spin lock、读写锁read/write。

自旋锁和互斥锁的区别是什么?(考察!)

(1)自旋锁的定义和优缺点:

自旋锁,它不会引起调用者睡眠,如果自旋锁已经被别的某一线程保持,那么该调用者不会进入睡眠状态,而是一直循环在那里看着 该自旋锁的保持者是否释放了该自旋锁。它的作用就是为了某项资源的互斥使用。因为自旋锁不会引起调用者进入睡眠状态,所以自旋锁的效率远高于互斥锁。虽然自旋锁的效率比较高,但是它仍然有一些不足之处:

自旋锁的调用者在未获得锁的情况下还一直在“自旋运行”、占用CPU,如果不能在短时间内获得所需要的“锁”,这无疑会使CPU的利用率下降。所以,自旋锁适用于锁使用者保持锁时间比较短的情况下。使用自旋锁时,在递归调用的时候有可能造成死锁。

(2)两种锁的加锁原理:

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换、CPU的抢占、信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁)、死循环检测锁的标志位、机制不复杂。

(3)闲等锁和忙等锁:

 互斥锁属于

sleep-waiting 类型的锁。(闲等待的锁)

 自旋锁是属于

busy-waiting 类型的锁。(忙等待的锁)

(要能说出来这个例子!)

例如,在一个双核的机器上有两个线程 ( 线程A和线程B ),它们分别运行在 内核Core0 和 内核Core1上。

现在假设,线程A 想要通过 pthread_mutex_lock
操作 去得到一个临界区的锁,而此时这个锁正被线程B 所持有,那么线程A
就会被阻塞 (blocking),内核Core0 会在此时进行上下文切换 ( Context Switch ) 将线程A 置于等待队列中,此时内核Core0 就可以运行其他的任务 ( 例如另一个线程C ) 而不必进行忙等待。

但是,自旋锁则不然,它是属于 busy-waiting 类型的锁(忙等待的锁)。如果线程A
是使用 pthread_spin_lock 操作去请求锁,那么线程A
就会一直在 内核Core0 上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

4)两种锁的应用场景:

自旋锁:主要就是用在临界区持锁时间非常短、而且CPU资源不紧张的情况下,自旋锁一般用于多核的服务。

互斥锁:主要用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑:

1)临界区有IO操作。

2)临界区循环量比较大。

3)单核处理器。

另:线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads 提供的锁机制(lock)来对多个线程之间的共享临界区(Critical
Section)进行保护。

进程之间通信的方式有哪些?线程之间通信的方式有哪些?(考察!)

·
进程间的通信方式:

(1) 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。

(2)有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。

(3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

(4)消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

(5)信号处理机制(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。

(6)共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。

(7)套接字(socket):套接字也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同及其间的进程通信。

几种方式的比较:

管道:速度慢、容量有限

消息队列:容量收到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。

信号量:不能传递复杂信息,只能用来同步。

共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。

·
线程间的通信机制:

(1)锁机制:

     1.1 互斥锁

mutex:提供了以排它方式阻止数据结构被并发修改的方法。

     1.2 读写锁

read/write:允许多个线程同时读共享数据,而对写操作互斥。

     1.3 条件变量

condtion variable:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁mutex
的保护下进行的。条件变量始终与互斥锁一起使用。

(2)信号量机制:比如常用的二进制信号量0和1。

(3)信号处理器机制:类似于进程间的信号处理。

线程之间的通信,主要目的是用于线程之间的同步,所以,线程没有象进程通信中用于数据交换的通信机制。

多线程程序架构,线程数量应该如何设置?

在多线程程序架构中,线程的数量应该和主机CPU 的核数相等,或者应该等于CPU核数+1 的个数。

另:在进行进一步深入讨论之前,先以提问的方式就一些共性认知达成一致。

提问:工作线程数是不是设置的越大越好?

回答:不是的。

1)服务器的CPU核数 有限,同时并发的线程数也是有限的,1核CPU设置10000个工作线程没有意义。

2)线程之间的切换是有开销的,如果线程切换过于频繁,反而会使性能降低。

提问:调用sleep() 函数的时候,线程是否一直占用CPU?

回答:不占用,调用sleep() 等待时会把CPU 让出来,给其他需要CPU 资源的线程使用。

不止调用sleep() 函数,在进行一些阻塞调用,例如网络编程中的阻塞
accept()【等待客户端连接】和 阻塞recv()【等待下游回包】也不占用CPU资源。

提问:如果CPU是单核,设置多线程有意义么,能提高并发性能么?

回答:即使是单核,使用多线程也是有意义的。

1)多线程编码可以让我们的服务/代码更加清晰:有些IO线程收发包,有些Worker线程进行任务处理,有些Timeout线程进行超时检测。

2)如果有一个任务一直占用CPU资源在进行计算,那么此时增加线程并不能增加并发。

例如这样的一个代码:

while(1) { i++; }

该代码一直不停的占用CPU资源进行计算,会使CPU占用率达到100%。

3)通常来说,Worker线程一般不会一直占用CPU进行计算,此时即使CPU是单核,增加Worker线程也能够提高并发,因为这个线程在休息的时候,其他的线程可以继续工作。

有一个计数器,多个线程都需更新,会遇到什么情况,原因是什么,该如何做呢?

·
可能一个线程更新的数据已经被另外一个线程更新过了,更新的数据就会出现异常。

·
方法:可以对这个计数器的操作代码加锁,保证计数器的更新只会被一个线程完成。

什么是原子操作?

(1)原子操作,指的是不会被线程调度机制打断的操作。这种操作一旦开始,就会一直运行下去直到结束,中间是不会有任何的上下文切换的。

(2)如果原子操作过程中出现了异常,那么之前所做的操作全部都原样撤回,撤回到执行这次原子操作之前的初始状态。

熟练netstat tcpdump
ipcs ipcrm

netstat: 检查网络状态,tcpdump:截获数据包,ipcs:检查共享内存,ipcrm:解除共享内存

共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?

将一块内存映射到两个或者多个进程地址空间。通过指针访问该共享内存区。一般通过mmap将文件映射到进程地址共享区。存在于进程数据段,最大限制是0x2000000Byte

ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意未初始化的数据放在bss段)

可执行连接格式。可以减少重新编程重新编译的代码。

动态链接和静态链接的区别?

动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在可执行文件运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了

32位系统一个进程最多有多少堆内存

32位意味着4G的寻址空间,Linux把它分为两部分:最高的1G(虚拟地址从0xC0000000到0xffffffff)用做内核本身,成为“系统空间”,而较低的3G字节(从0x00000000到0xbffffff)用作各进程的“用户空间”。每个进程可以使用的用户空间是3G。虽然各个进程拥有其自己的3G用户空间,系统空间却由所有的进程共享。从具体进程的角度看,则每个进程都拥有4G的虚拟空间,较低的3G为自己的用户空间,最高的1G为所有进程以及内核共享的系统空间。实际上有人做过测试也就2G左右。

写一个c程序辨别系统是64位 or 32位

void* number =
0;
printf("%d\n",sizeof(&number));

输出8就是64位 输出4就是32位的 根据逻辑地址判断的

写一个c程序辨别系统是大端or小端字节序

union{ short value;
char a[sizeof(short)];}test;

test.value= 0x0102;

if((test.a[0] == 1)
&& (test.a[1] == 2)) cout << “big”<<endl; else
cout << “little”
<< endl;

信号:列出常见的信号,信号怎么处理?

1).进程终止的信号

2).跟踪进程的信号

3).与进程例外事件相关的信号等

对于信号的处理或者执行相关的操作进行处理或者直接忽略

i++ 是否原子操作?并解释为什么?

答案肯定不是原子操作,i++主要看三个步骤

首先把数据从内存放到寄存器上,在寄存器上进行自增处理,放回到寄存器上,每个步骤都可能会被中断分离开!

说出你所知道的各类linux系统的各类同步机制(重点)(每个技术面试官必问)

1).原子操作

2).信号量(其实就是互斥锁也就是锁的机制)

3).读写信号量(就是读写锁)

4).自旋锁

5).内核锁

6).顺序锁

死锁就是几个进程申请资源,出现了循环等待的情况!

避免死锁的方法:

1).资源是互斥的 2).不可抢占 3)占有且申请
4).循环等待

什么是死锁?产生条件?如何避免死锁

·
死锁的概念:在2个或多个并发进程中,如果每个进程持有某有资源而又都等待别的进程释放它或他们现在保持的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是2个或多个进程被无限期地阻塞、相互等待的一种状态。

·
死锁产生的原因:系统资源不足,进程推进顺序非法

·
产生死锁的必要条件:

1.互斥条件:一个资源每次只能被一个进程使用

2.不可剥夺条件:进程已获得资源,在未使用完之前,不能被其他进程强行剥夺,只能主动释放

3.请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

4.循环等待条件:即进程集合{p0,p1,p2,p3……pn};p0正在等待p1占用的资源,p1正在等待p2占用的资源,pn正在等待p0占用的资源。

只要上述一个条件不成立,就不会发生死锁。

·
避免死锁的方法:

1).资源是互斥的 2).不可抢占 3)占有且申请
4).循环等待

  1. exit() _exit()的区别?

  2. 如何实现守护进程?

1)创建子进程,父进程退出

2)在子进程中创建新会话

3)改变当前目录为根目

4)重设文件权限掩码

  1. 关闭文件描述符

  2. 守护进程退出处理

当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。

  1. linux的任务调度机制是什么?

Linux分实时进程和普通进程,实时进程应该先于普通进程而运行。实时进程:

1) FIFO(先来先服务调度)

2) RR(时间片轮转调度)。

每个进程有两个优先级(动态优先级和实时优先级),实时优先级就是用来衡量实时进程是否值得运行的。 非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级越高,得到CPU时间的机会也就越大。

  1. 标准库函数和系统调用的区别?

系统调用:是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。inux内核是单内核,结构紧凑,执行速度快,各个模块之间是直接调用的关系。linux系统上到下依次是用户进程->linux内核->硬件。其中系统调用接口是位于Linux内核中的,整个linux系统从上到下可以是:用户进程->系统调用接口->linux内核子系统->硬件,也就是说Linux内核包括了系统调用接口和内核子系统两部分;或者从下到上可以是:物理硬件->OS内核->OS服务->应用程序,操作系统起到“承上启下”作用,向下管理物理硬件,向上为操作系服务和应用程序提供接口,这里的接口就是系统调用了。

库函数:把函数放到库里。是把一些常用到的函数编完放到一个lib文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就可以了。一类是c语言标准规定的库函数,一类是编译器特定的库函数。

系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。

  1. 系统如何将一个信号通知到进程?

内核给进程发送信号,是在进程所在的进程表项的信号域设置对应的信号的位。进程处理信号的时机就是从内核态即将返回用户态度的时候。执行用户自定义的信号处理函数的方法很巧妙。把该函数的地址放在用户栈栈顶,进程从内核返回到用户态的时候,先弹出信号处理函数地址,于是就去执行信号处理函数了,然后再弹出,才是返回进入内核时的状态。

  1. fork()一子进程程后父进程的全局变量能不能使用?

fork后子进程将会拥有父进程的几乎一切资源,父子进程的都各自有自己的全局变量。不能通用,不同于线程。对于线程,各个线程共享全局变量。

  1. 什么是临界区、如何解决冲突?

每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。如果有若干个进程要求进入空闲的临界区,一次仅允许一个进程进入。任何时候,处于临界区的进程不可多于一个。如已有进程进入自己的临界区,则其他试图进入临界区的进程必须等待。进入临界区的进程要在有限时间内退出,以便其他进程能及时进入自己的临界区。如果不能进入自己的临界区,就应该让出CPU,避免进程出现忙等等现象。

  1. 分段和分页的区别?

页是信息的物理单位,分页是为了实现离散分配方式,以减少内存的外零头,提高内存的利用率。分页仅仅是由于系统管理的需要,而不是用户的需要。

段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好的满足用户的需要。

页的大小固定且由系统确定,把逻辑地址分为页号和页内地址两部分,由机器硬件实现的。因此一个系统只能有一种大小的页面。段的长度却不固定,决定于用户所编写的程序,通常由编写程序在对源代码进行编辑时,根据信息的性质来划分。

分页的作业地址空间是一维的,即单一的线性空间。

分段的作业地址空间是二维的,程序员在标识一个地址时,既需要给出段名,又需要给出段内地址。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值