嵌入式八股之Linux应用开发
#牛客在线求职答疑中心##嵌入式##秋招##面试##24届软开秋招面试经验大赏#Written with StackEdit中文版.
异步IO和同步IO区别
**答案:**如果是同步IO,当一个IO操作执行时,应用程序必须等待,直到此IO执行完。相反,异步IO操作在后台运行,IO操作和应用程序可以同时运行,提高系统性能,提高IO流量。
**解读:**在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO中,线程发送一个IO请求到内核,然后继续处理其他事情,内核完成IO请求后,将会通知线程IO操作完成了。
进程间通信方式
有名管道
半双工的通信方式,但是它允许用于无亲缘关系的进程之间的通信。
信号量
是一个计数器,通常作为一种同步机制,用于进程和线程间的同步
消息队列
是一个消息链表,存放在内核中并且由消息队列标识符标识。
共享内存
一段能够被多个进程共同访问的内存,由一个进程创建。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而设计的,往往与其他通信方式如信号量配合使用,来实现进程间同步与通信。
套接字
可用于不同主机间的进程通信。
信号
用于通知接收进程某个事件已经发生
进程的地址空间模型
data segment
存储初始化不为0的全局变量和静态变量、const型常量。
bss segment
存储未初始化的、初始化为0的全局变量和静态变量。
heap(堆)
用于动态开辟内存空间。
memory mapping space(内存映射区)
mmap系统调用使用的空间,通常用于文件映射到内存或匿名映射(开辟大块空间),当malloc大于128k时(此处依赖于glibc的配置),也使用该区域。在进程创建时,会将程序用到的平台、动态链接库加载到该区域。
stack(栈)
存储函数参数、局部变量。
kernel space
存储内核代码
进程的状态
- 就绪态:所有运行条件已就绪,只要得到了CPU时间就可运行。
- 运行态:得到CPU时间正在运行。
- 僵尸态:进程已经结束了但父进程还没来得及回收。
- 等待态:包括浅度睡眠跟深度睡眠。进程在等待某种条件,条件成熟后即进入就绪态。浅度睡眠时进程可以被信号唤醒,但深度睡眠时必须等到条件成熟后才能结束睡眠状态。
- 暂停态:暂时停止参与CPU调度(即使条件成熟),可以恢复。
子进程从父进程继承的资源有哪些?
**答案:**子进程继承父进程的绝大部分资源,包括堆栈、内存、用户号和组号、打开的文件描述符、当前工作目录、根目录。
**注意:**子进程独有进程号、不同的父进程号、自己的文件描述符。
在linux系统中,当发生以下两种情况时,父进程会收到子进程的信号:
- 当父进程的某个子进程终止时,父进程会收到SIGCHLD信号;
- 当父进程的某个子进程因收到信号而停止(暂停运行)或恢复时,也可能向父进程发送信号。
- 当子进程申请内存失败时或者当子进程进入阻塞状态时,这都是子进程自己独立运行状态,不会通知到父进程
进程和线程的区别
- 进程是系统中程序执行和资源分配的基本单位,线程是CPU调度的基本单位。
- 一个进程个拥有多个线程,线程可以访问其所属进程地址空间和系统资源(数据段、已经打开的文件、I/O设备等),同时也拥有自己的堆栈。
- 同一进程中的多个线程可以共享同一地址空间,因此它们之间的通信实现也比较简单,而且切换开销小、创建和消亡的开销也小。而进程间的通信则比较麻烦,而且进程切换开销、进程创建和消亡的开销也比较大。
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立 执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程可以独立运行吗?一个线程崩溃会导致整个进程崩溃吗
- 线程不能独立运行,但一个线程崩溃不一定导致整个进程崩溃。
- 线程属于进程,线程的运行需要依赖进程的地址空间和系统资源。
- 线程崩溃的本质就是内存出错,若出错的内存没有被其他线程访问,则不会导致其他线程出错,也就不会导致进程崩溃。
线程间通信和同步方式有哪些?
- 信号、信号量、互斥锁、条件变量、自旋锁、读写锁。
互斥锁与信号量的区别?
- 信号量用于线程同步,互斥锁用于线程互斥。
- 信号量可以为非负整数,可以实现多个同类资源的多线程同步;互斥锁只能为0/1,只能用于一个资源的互斥访问。
- 信号量可以由一个线程释放,另一个线程得到;互斥锁的加锁和解锁必须由同一线程分别对应使用,且多个线程使用多个互斥锁必须注意统一顺序,否则可能造成死锁。
自旋锁和信号量的区别
1.由于争用信号量的进程在等待锁重新变为可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。2.相反,锁被短时间持有时,使用信号量就不太适宜了,因为睡眠引起的耗时可能比锁被占用的全部时间还要长。5.在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。6.信号量锁保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区,因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一进程企图获取本自旋锁,死锁就会发生。7.信号量不同于自旋锁,它不会禁止内核抢占(自旋锁被持有时,内核不能被抢占),所以持有信号量的代码可以被抢占,这意味着信号量不会对调度的等待时间带来负面影响。
多线程的优点
- 更加高效的内存共享。多进程下内存共享不便
- 较轻的上下文切换。因为不用切换地址空间,CR3寄存器和清空TLB
多进程的优点
- 各个进程有自己内存空间,所以具有更强的容错性,不至于一个集成crash导致系统崩溃
- 具有更好的多核可伸缩性,因为进程将地址空间,页表等进行了隔离,在多核的系统上可伸缩性更强
创建进程的方式
- 系统初始化(查看进程inux中用ps命令,windowst中用任务管理器,前台进程负责与用户交互,后 台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、 web页面、新闻、打印)
- 一个进程在运行过程中开启了子进程(如nginx开启多进程,os. fork,subprocess Popen等)
- 用户的交互式请求,而创建一个新进程(如用户用鼠标双击任意款软件图片:q微信暴风影音等)
死锁相关
死锁出现的必要条件
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
死锁的恢复方式
通过抢占进行恢复在某些情况下,可能会临时将某个资源从它的持有者转移到另一个进程。比如在不通知原进程的情况下,将某个资源从进程中强制取走给其他进程使用,使用完后又送回。这种恢复方式一般比较困难而且有些简单粗暴,并不可取。通过回滚进行恢复进程的检测点意味着进程的状态可以被写入到文件以便后面进行恢复。检测点不仅包含
存储映像(memory image)
,还包含资源状态(resource state)
。为了进行恢复,要从上一个较早的检查点上开始,这样所需要资源的进程会回滚到上一个时间点,在这个时间点上,死锁进程还没有获取所需要的资源,可以在此时对其进行资源分配。杀死进程恢复最简单有效的解决方案是直接杀死一个死锁进程。但是杀死一个进程可能照样行不通,这时候就需要杀死别的资源进行恢复。如何破坏死锁
避免死锁
- 对共享资源操作前一定要获得锁
- 完成操作后一定要释放锁
- 尽量短时间的占用锁
- 如果有 多个锁,释放顺序要和获得顺序相同
进程状态
- 运行状态(就绪态和运行态) 表示进程在运行队列中,处于正在执行 或即将运行状态,只有在该状态的进程才可能在 CPU 上运行
- 可中断的睡眠状态(阻塞态) 处于这个状态的进程因为等待某种事件的发生而被挂起,比如进程 在等待信号。
- 不可中断的睡眠状态 通常是在等待输入或输出(I/O)完成,处于这种状态的进程不能响应异步信号。处于这 种状态的进程都是在等待输入或输出(I/O)完成,在等待完成后自动进入“就绪态”。
- 停止状态 通常是被 shell 的工作信号控制,或因为它被追踪,进程正处于调试器的控制之下。当进程收到 SIGSTOP 或者 SIGTSTP 中的其中一个信号时,进程状态会被置为暂停态, 该状态下的进程不再参与调度,但系统资源不会被释放,直到收到 SIGCONT 信号后被重新 置为就绪态。
- 退出状态(被回收) 正常退出:main 函数内 return 或者调用 exit() 函数或者线程调用 pthread_exit()
- 退出状态(僵尸进程)
- 僵尸进程是一种非常特殊的进程,它已经放弃了几乎所有的内存空间,没 有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等 信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。 调用 do_exit() 函数来使得进程的状态变成僵尸态
用户空间与内核空间有哪些通信方式?1、系统调用,提供特定的用户空间与内核空间的信息传递。
2、信号,内核空间出现一些异常时候会发送信号给进程,如SIGSEGV、SIGPIPE等。
3、/proc,proc可以读取内核空间的配置信息和运行状态并且设置部分属性的值。
4、文件,可以通过指定文件的读写操作来实现通信。
5、netlink,类似socket通信方式,可以读写大量的数据,实现稍微复杂。
6、ioctl,可以实现数据量比较少时候的通信
Linux内核编译
- Linux内核编译的方式有两种:一种是在原有的Linux系统上编译内核,另一种是在一个干净的环境中编译内核。在原有的Linux系统上编译内核,需要先安装必要的工具和库,然后下载内核源代码,进行配置、编译和安装。
linux错误处理
erron变量
- 当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给 errno 变量,它是程序中的全局变量,该变量用于存储就近发生的函数执行错误编号,也就意味着下一次的错误码会覆盖上一次的错误码。
strerror函数
#include <string.h> char *strerror(int errnum); **errnum**:错误编号 errno。 **返回值**:对应错误编号的字符串描述信息。perror函数
#include <stdio.h> void perror(const char *s);
- 调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值,调用此函数会直接将错误提示字符串打印出来
内核态和用户态
- 当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。
- 在内核态下,进程运行在内核地址空间中,此时CPU可以执行任何指令。运行的代码也不受任何的限 制,可以自由地访问任何有效地址,也可以直接进行端口的访问。 在用户态下,进程运行在用户地址空间中,被执行的代码要受到CPU的诸多检查,它们只能访问映射其 地址空间的页表项中规定的在用户态下可访问页面的虚拟地址。
- 通过区分内核空间和用户空间的设计,隔离了操作系统代码(操作系统的代码要比应用 程序的代码健士很多)与应用程序代码。即便是单个应用程序出现错误,也不会影响到操作系统的稳定 性。所以,区分内核空间和用户空间本质上是要提高操作系统的稳定性及可用性。
内核态和用户态通信方式
- 使用API,如
copy_from_user/copy_to_user
- 信号,如内核发送信号杀死相对应进程
- 使用proc文件系统
内核功能
- I/O指令、置终端屏蔽指令、清内存、建存储保护、设置时钟指令。
- 中断、异常、陷入,比如缺页中断等
- 进程(线程)管理
- 系统调用,比如调用了设备驱动程序
- 用户内存地址的转换(逻辑--->物理映射)
什么情况会导致用户态到内核态切换
- 系统调用:用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork()就是一个创建新进程的系统调用,系统调用的机制核心使用了操作系统为用户特别开放的一个中断来实现,如Linux 的 int 80h 中断,也可以称为软中断
- 异常:当 C P U 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
- 中断:当 C P U 在执行用户态的进程时,外围设备完成用户请求的操作后,会向 C P U 发出相应的中断信号,这时 C P U 会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。
硬链接和软链接
- 硬连接指通过索引节点来进行连接。与普通文件没什么不同, inode 都指向同一个文件在硬盘中的区块。
- 硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能
- 只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放
- 软连接:文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
ln f1 f2 #创建f1的一个硬连接文件f2 ln -s f1 f3 #创建f1的一个符号连接文件f3
边学习边总结的嵌入式各种知识,八股,面经,量大管饱,最重要:免费开放,希望大家能共同进步。