本章详情: 该文章是我在招阶段整理的关于“嵌入式面经”的一些道题目,“软件开发岗位”也可以参考下。涵盖了大多数嵌入式/软件开发岗位常见基础面试题,题目来源是牛客网各大大厂的面经,答案是我逐个查阅资料,理解整理出的答案争取让读者尽可能的容易阅读和理解,部分内容来源于牛客网用户:爱打球的程序员乔丹。资深嵌入式软件开发工程:任立超。
UNIX高编阶段常见习题整理
目录
13、main return和exit和_exit进程终止有什么区别
一、文件系统与IO
1、 Linux内核空间模型
1)栈区:由编译器自动分配和释放,存放函数的参数值(形
参)、局部变量(int a =1;还有指针变量)等,其操作方式类似于数据结构中的栈,先进后出。
2)堆区:一般由程序员分配和释放,若程序员不释放,可能会造成内存泄漏,程序结束的时候可能由操作系统回收,注意它与数据结构中的堆是两回事,分配方式类似于链表。
3)全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(.data),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss),程序结束后系统释放。
4)文字常量区:常量字符串放在这里,程序结束后有系统释放。
5)程序代码区(.text):存放函数体的二进制代码。
栈的空间有限,堆是很大的自由存储区,程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也是在栈上进行。
注意:64位操作系统下的虚拟内存空间大小:地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要2^64那么大的寻址空间,过大的空间只会造成资源的浪费。所以64位Linux一般使用48位表示虚拟空间地址,40位标识物理地址。0x0000000000000000~0x00007fffffffffff表示用户空间, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示内核空间,共提供256TB(2^48)的寻址空间。
2、标准I/O和文件I/O有什么区别
-
标准I/O有缓存区 文件I/O没有缓存区
-
标准I/O是库 文件I/O是系统调用
-
标准I/O可移植性好 文件I/O速度快
-
标准I/O通过文件流标志打开的文件 文件I/O通过文件描述符标志打开的文件
3、缓存区的类型和刷新缓存区的方法
缓冲区:暂存空间,大多数情况下,缓冲区的存在是件好事,作用合并系统调用
行缓冲:stdout,换行时,满了时,强制刷新 全缓冲:默认,(只要不是终端设备,全采用全缓冲模式)满了时,强制刷新 无缓冲:stderr,需要立即输出
4、文件的元信息和文件类型
(1)元信息
-
文件的类型和权限
-
用户ID
-
组ID
-
文件字节数
-
文件块大小
-
最后一次访问时间
-
最后一次修改时间
-
最后一次改变时间(属性)
-
文件的设备编号
(2)文件类型
-
- 普通文件regular file
-
d 目录文件dirctory
-
b 块设备文件block device
-
c 字符设备文件character device
-
s 套接字文件socket
-
p 管道文件 pipe
-
l 链接文件link
5、如何得到文件拥有者的名字
通过stat()函数得到uid,用getpwuid()读出/etc/passwd每一个字段,并且存放在struct passwd结构体中,结构体中就包括用户名
6、如何读目录
opendir()得到一个目录流,通过目录流读取目录项中的每一个,通过readdir()得到目录项,多次调用时会偏移到每一个元素,当读到最后一个目录流,readdir()返回NULL,之后再closedir()
7、请用3种不同的方法来获取文件长度/大小
-
fseek(fp,0,SEEK_END) ---- num = ftell(fp);
-
lseek(),操作成功返回的是新的文件位置偏移量
-
stat():获取文件的详细信息,然后存储结构体里
-
fgetc() :一个一个字节读
二、进程、线程、信号
1、什么是进程,线程?也就是问彼此有什么区别?
进程:资源(CPU、内存等)分配的基本单位 线程:CPU调度和分配的基本单位
1)当我们运行一个程序的时候,系统就会创建一个进程,并分配地址空间和其他资源,最后把进程加入就绪队列直到分配到CPU时间就可以正式运行了。
2)线程是进程的一个执行流,有一个初学者可能误解的概念,进程就像一个容器一样,包括程序运行的程序段、数据段等信息,但是进程其实是不能用来运行代码的,真正运行代码的是进程里的线程。
3)那么,来看看我们最熟悉的main()函数,我们既可以认为这是一个进程,也可以认为是一个线程。我们都知道,在C/C++中main函数是程序入口,所以准确来说main函数是程序的主线程。然而很神奇的地方在于,当系统在执行main函数的时候,main函数又是一个独立的进程,我们可以在main函数里创建子进程,也可以创建子线程。
4)在main函数里创建的多个子线程中,每个线程有自己的堆栈和局部变量,但多个线程也可共享同个进程下的所有共享资源,因此我们经常可以创建多个线程实现并发操作,实现更加复杂的功能。
2、进程线程的状态转换图,什么时候阻塞,什么时候就绪?
创建态(New):一个进程正在被创建,还没到转到就绪状态之前的状态。 就绪态(Ready):一个进程获得了除CPU时间片之外的一切所需资源,一旦得到CPU时间片调度时即可运行。 运行/执行态(Running):当一个进程得到CPU调度正在处理机上运行时的状态。 睡眠/挂起态:由于某些资源暂时不可得到而进入“睡眠态”,将进程挂起,等待唤醒。 阻塞/暂停态(Blocked):一个进程正在等待某一事件而暂停运行时,如等待某资源成为可用,或等待文件读取完成等。 结束/僵尸态(Exit):一个进程正在从系统中消失时的状态,这是因为进程结束或其它因流产所导致。 死亡态:进程生命周期结束了,将所占用的资源还给系统。
我们从父进程调用fork()创建子进程开始讲起,此时子进程处于创建态,此时系统为进程分配地址和资源后将进程加入就绪队列,进入就绪态。就绪态的进程得到CPU时间片调度正式运行,进入执行态。执行态有四种常见结果:
1)当时间片耗光或者被其他进程抢占,则重新进入就绪态,等待下一次CPU时间片;
2)由于某些资源暂时不可得到而进入“睡眠态”(如欲读取的文件为空或者欲获得的某个锁还处于不可获得状态),等待资源可得后再唤醒,唤醒后进入就绪态;
3)收到SIGSTOP/SIGTSTP信号进入暂停态,直到收到SIGCONT信号重新进入就绪态;
4)进程执行结束,通过内核调用do_exit()进入僵尸态,等待系统回收资源。当父进程调用wait()/waitpid()后接收结束子进程,该进程进入死亡态。
3、父进程、子进程的关系以及区别
子进程继承父进程:
○用户号UIDs和用户组号GIDs
○环境Environment
○堆栈
○共享内存
○打开文件的描述符
○执行时关闭(Close-on-exec)标志
○信号(Signal)控制设定
○进程组号
○当前工作目录
○根目录
○文件方式创建屏蔽字
○资源限制
○控制终端
子进程独有的:
○进程号PID
○不同的父进程号
○自己的文件描述符和目录流的拷贝
○子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
○不继承异步输入和输出
答:子进程从父进程继承的主要有:用户号和用户组号;堆栈;共享内存;目录(当前目录、根目录);打开文件的描述符;但父进程和子进程拥有独立的地址空间和PID参数、不同的父进程号、自己的文件描述符。
4、标准信号和实时信号的区别
信号基本概念:软件模拟中断,进程接受信号后做出相应响应(信号从收到响应依赖于中断,但信号不是中断) 信号会打断阻塞的系统调用,open,read等都是阻塞的,会有发生假错(EINTR)的情况
标准信号会丢失,即使发送多个也只会响应一个。 实时信号的响应有固定的顺序,不会丢失,会响应多次。
信号编号(1--31:标准信号,34--64:实时信号)
6、原子操作
原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).
第一步:设置当前进程信号集状态为 目标信号集状态mask,并保存进程旧的信号集状态为 oldmask 第二步:马上进入等待信号阶段 第三步:收到信号唤醒后 设置当前进程信号集状态为 旧的信号集状态oldmask。
7、进程、多线程的优缺点
进程和线程最大的区别和联系,一个进程由PCB(进程控制块)、数据段、代码段组成,进程本身不可以运行程序,而是像一个容器一样,先创建出一个主线程,分配给主线程一定的系统资源,这时候就可以在主线程开始实现各种功能。当我们需要实现更复杂的功能时,可以在主线程里创建多个子线程,跟人多好干活的道理一样,多个线程在同一个进程里,利用这个进程所拥有的系统资源合作完成某些功能。
1)多进程更健壮,一个进程死了不影响其他进程,子进程死了也不会影响到主进程,毕竟系统会给每个进程分配独立的系统资源。多线程比较脆弱,一个线程崩溃很可能影响到整个程序,因为多个线程是在一个进程里一起合作干活的。
2) 进程性能大于线程,每个进程独立地址空间和资源,而多个线程是一起共享了同个进程里的空间和资源,结果就很明显了,线程的性能上限一定比不上进程。
3) 正因为进程性能大于线程。所以这也引发了另一重要知识点,创建多进程的系统花销远大于创建多线程。
4)多进程通讯因为需要跨越进程边界,不适合大量数据的传送,更适合小数据或者密集数据的传送。而多线程无需跨越进程边界,适合各线程间大量数据的传送,甚至还有很重要的一点,多线程可以共享同一进程里的共享内存和变量哦。
5) 多进程逻辑控制比多线程复杂,需要与主进程做好交互。根据上面几点,我们不难知道多进程是“要用来做大事”的,而多线程是“各自做件小事,合作完成大事”。所以要做大事自然就需要更复杂的逻辑控制,不像做小事那么目标明显。
6)虽然多线程逻辑控制比较简单,但是却需要复杂的线程同步和加锁控制等机制。
8、什么时候用进程,什么时候用线程
1)创建和销毁较频繁使用线程,因为创建进程花销大嘛。
2)需要大量数据传送使用线程,因为多线程切换速度快,不需要跨越进程边界。
3)并行操作使用线程。线程是为了实现并行操作的一个手段,也就是刚才说的需要多个并行操作“合作完成大事”,当然是使用线程啦。
4)最后可以总结为:安全稳定选进程;快速频繁选线程;
示例:调用外部程序mplayer来播放视频程序框架是,因为播放视频需要的系统资源较大,所以在main函数里先创建一个子进程用来执行播放视频的代码,此时子进程播放视频不影响main函数。最后,main函数执行while(1)死循环一直等待有没有上一部、下一部按键按下,很明显,此时main函数在死循环里无法检验按键按下情况,而检验按键是一件容易且与主线程main函数息息相关的事情,此时就创建子线程来检验按键即可。
最终,子线程检验到“下一部”按键按下,告知主线程main函数,main函数就立刻再次创建子进程再次调用外部程序mplayer播放下一个视频。
9、一个进程可以创建多少线程,和什么有关
一个进程创建线程的个数由虚拟内存和分配给线程的调用栈大小决定。 一个线程的栈的大小可以通过ulimit -s指令来查看,一般大多是8M-10M。
10、多进程、多线程同步(通讯)的方法
进程间通讯: (1)管道/无名管道(2)信号(3)共享内存(4)消息队列(5)信号量(6)socket 注意:临界区则是一种概念,指的是访问公共资源的程序片段,并不是一种通信方式。
线程通讯: (1)信号量(2)读写锁(3)条件变量(4)互斥锁(5)自旋锁
11、互斥锁与信号量的区别?
互斥锁用于线程的互斥,信号量用于线程的同步。这是互斥锁和信号量的根本区别,也就是互斥和同步之间的区别。同时互斥锁的作用域仅仅在于线程,信号量可以作用于线程和进程。
12、进程启动和终止的方式!!!
启动:
-
内核有C启动历程,子进程通过exec用C启动历程替换子进程
-
启动历程调用main函数使得整个历程启动起来
终止:
5种为正常终止
-
从main返回;
-
调用exit:
-
调用_exit或Exit:
-
最后一个线程从其启动例程返回:
-
从最后一个线程调用pthread_exit 。
异常终止有3种方式,它们是:
-
调用abort ;
-
接到一个信号;
-
最后一个线程对取消请求做出响应。
13、main return和exit和_exit进程终止有什么区别
-
return是返回函数值并退出函数
-
exit结束之前会清理I/O缓存和终止处理程序
-
_exit直接终止程序
14、什么是守护进程
-
pid和ppid和pgid相同
-
不占用控制终端
15、vfork和fork的差别
(1)fork :子进程拷贝父进程的数据段,代码段
vfork:子进程与父进程共享数据段
(2)fork父子进程的执行次序不确定
vfork保证子进程先运行
16、匿名管道的特征
-
有空间大小,只能进行单向通信;
-
只适用于有血缘关系之间的进程;
-
不能用lseek来定位文件偏移量;
-
在运用管道时把不必要的文件关掉,必须有读写双方;
-
当进程读到一个空管道时会处于阻塞态,直到数据到来。没有写端,返回eof表示读完;
-
当进程写到一个满管道时会处于阻塞态,直到数据读走。没有读端只有写端,会产生信号结束进程