c++系统编程复习


Q1 C++静态库和动态库的区别?

  1. 静态库是在链接阶段,将汇编生成的目标文件和引用到的库一起链接打包到可执行文件中,而动态库则是在程序运行时才会被载入。

  2. 静态库的相关函数库在链接时已经被打包了,所以移植很方便。
    动态库需要在移植程序时将库文件.dll or .so一起移植。

  3. 因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行的文件。如果多个进程都需要共享某些函数,则存在空间浪费问题。
    而动态库一个可以实现进程之间共享(生成地址无关的编译程序)。

  4. 静态库库函数更新后需要重新打包所有使用到该静态库的目标程序。而动态库只需重新生成.so文件进行替换即可。

Q2 动态库加载失败?

首先用ldd命令查看程序到底会使用哪些动态库,加载动态库默认是从/lib,/usr/lib等路径中搜索,从/etc/ld.so.conf中配置的路径搜索以及环境变量LD_LIBARARY_PATH。

Q3 虚拟地址空间?文件描述符?

操作系统会给每个进程对应一个虚拟地址空间,对于32位操作系统,地址空间最多为4GB,2^32B。其中内核空间占1GB,用户空间占4GB。

内核空间里面有进程控制块PCB,PCB里面有文件描述符表,用于标记该进程所打开的所有文件。

用户区从低地址往高地址分别为保留区、代码区、全局静态区(数据段,其中已经初始化的存在.data部分 没初始化的存在.bss部分)、堆区、内存映射区、栈区(分配时从高地址到低地址来存)。

Q4 进程之间的转换?

分为运行态、就绪态、阻塞态。
运行态可以转到就绪态和阻塞态。
运行态->就绪态的原因是时间片到了。
运行态->阻塞态的原因是因为需要等待I/O等事件完成。
就绪态只能到运行态,原因是因为被调度。
阻塞带只能到就绪态,是因为某个等待的I/O事件完成。

Q5 进程的创建?Fork及父子进程的虚拟地址空间?

主要是通过fork函数来创建进程,命令行本身也是一个进程,会复制一个和父进程一模一样的副本。

fork函数创建进程后,父进程会返回子进程的pid,而子进程会返回-1。 fork在复制父进程空间的时候,会使用写时复制技术,也就是说不会真的复制一份物理内存,只有当对应代码段被写入时才会对其进行内存复制。

Q6 exec函数族?

在调用进程内部执行一个可执行文件,然后取代调用进程本身的地址空间。通常是通过fork一个子进程,然后在子进程中调用来实现。

Q7 进程退出、僵尸进程、孤儿进程、怎么解决僵尸进程?

进程退出使用exit函数或者_exit函数,其中_exit函数属于系统调用,会立即退出进程并且关闭文件描述符、将子进程的交给进程号为1的进程的子进程,向父进程发送sigchild信号。

exit函数会调用_exit函数,在调用前会将缓冲区的数据先写入到文件。

僵尸进程是由于子进程运行结束,父进程没有使用wait或者waitpid来对子进程的资源进行回收造成的。

孤儿进程是指父进程先于子进程退出了,子进程的父进程就会变成init进程(进程号为1),并有init进程来对它们完成状态收集工作。

僵尸进程无法杀死,是因为僵尸进程本来就已经死了,就等着父进程收尸。解决问题的关键在于要通过父进程来对僵尸进程的资源进行回收,譬如父进程在收到sigchild信号后执行wait函数来对子进程的资源进行回收。
当然还有一个办法就是直接解决父进程,换一个更负责任的父进程(init进程,进程号为1)

Q8 进程间通信IPC的方式及其原理?

  • 管道(匿名、有名)
    匿名管道无文件实体(使用内核内存区的缓冲器),有名管道有文件实体(但是不存储数据)。
    管道数据的传输是半双工的,数据一旦被读走就在管道中被抛弃。
    匿名管道只能在有关系的进程之间使用。

读管道: 有数据,read返回实际读到的字节数,无数据,(写端全关 read返回0,相当于EOF,写端未完全关闭read阻塞)。
写管道:管道读端全部关闭,产生SIGPIPE信号,进程异常终止。读端没有全部关闭,管道已满,write阻塞,管道没有满,写入数据,返回写入的字节数。

有名管道相比管道多了一个路径名,通过路径名来确定是哪个管道,使用的还是内核缓冲区,也就可以实现进程间通信。

  • 内存映射
    将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

  • 共享内存

允许一个或多个进程共享同一块物理内存区域,速度很快但是存在写入冲突的问题,需要进程间同步。

共享内存与内存映射的区别,

1.共享内存可以直接创建,内存映射需要文件。

2.共享内存效率更高。

3.内存。共享内存都共享同一片内存空间,内存映射区每个进程在自
己的地址空间都有独立的内存。

4.数据安全。

进程意外退出。共享内存还在,内存映射区消失。

电脑意外死机,共享内存中的数据没了,内存映射区没了。但是磁盘文件还在,也就是内存映射区的数据还在。

5.生命周期
内存映射区:进程退出,内存映射区销毁。
共享内存在进程退出后还在,要么标记删除(所有关联进程数为0自动删除),或者关机。如果一个进程直接退出,会自动取消和共享内存的关联。

  • 信号

通过发送信号来进行进程间通信,最常见的就是通过sigkill来杀死进程,通过sigchild信号来提示父进程子进程已经停止,需要回收资源等。

Q9 定时器?信号捕捉?SIGCHILD信号?

可以通过alarm函数来设置闹钟时间,如果已经有了一个闹钟,那就返回上一个闹钟的剩余时间,否则返回0,不阻塞。
时间到了后会给进程发送SIGALRM信号,默认的处理动作是终止当前进程,可以通过sigaction函数来捕捉该信号,并且调用相关处理函数。

还有setitimer函数可以循环发送SIGALRM信号,并且能够精确到微秒。

信号捕捉使用sigaction函数。之所以不使用signal是因为signal不能设置屏蔽其他信号到来,从而产生二次中断。而sigaction则可以使得信号处于未决状态等待下次中断处理。

Q10 守护进程的概念?怎么创建?

守护进程是一个不受终端控制的在后台运行的的孤儿进程。

要想创建守护进程,需要以下步骤

  1. 父进程fork一个子进程后,父进程退出。

  2. 子进程调用setsid,使得子进程成为新会话组长和新的进程组长,同时失去控制终端。

  3. 忽略SIGUP信号,会话组长进程终止会向其他进程发送该信号,造成其他进程终止。

  4. 进制进程重开控制终端。会话组长可以重新申请打开一个终端,为了避免这种情况,可以再fork一个子进程,并且使调用fork的进程退出。

  5. 关闭不需要的文件描述符。

  6. 将当前的目录改为根目录。

  7. 使用unmask(0)将屏蔽字清零,防止访问文件权限受限。

  8. 处理SIGCHLD信号,譬如将其操作设置为SIG_IGN,这样就不会产生僵尸进程了。

Q11 线程和进程的区别?

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

  2. 进程拥有系统分配的资源,但是线程没有。

  3. 创建进程系统的开销明显大于创建线程。

  4. 一个进程可以有多个线程,至少有一个线程。

  5. 同一进程间的线程可以共享进程的资源,进程间不行。

Q12 条件变量和信号量?

  1. 使用条件变量可以一次唤醒所有等待者,而这个信号量没有的功能。

  2. 信号量是有一个值(状态的),而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。因为信号量有一个状态,如果想精准的同步,那么信号量可能会有特殊的地方。信号量可以解决条件变量中存在的唤醒丢失问题。

Q13 多线程实现原理?

把不同线程的代码分开存储在.text段。

每个线程有独享的栈空间(从进程的栈空间分配)。

每次线程调度都从线程自身的程序计数器所指的代码段开始执行。


文章作者: 青椒
版权声明: 本笔记所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 青椒 !
 上一篇
力扣刷题纪念 力扣刷题纪念
之前大一的时候就感觉到纯粹刷题的乐趣,后来因为各种各样的因素,学校对各类程序设计竞赛的支持力度也不是特别大,就放弃了。后来在研究生继续找回了这个乐趣。 说起在力扣刷题也有一段时间了,一开始觉得题目不会特别难,就没怎么做。后来发现大家都在
2022-03-30 青椒
下一篇 
金发姑娘和N头牛 金发姑娘和N头牛
原题链接https://www.acwing.com/problem/content/1954/ 题目分析根据题目,把每头奶牛在不同温度下的产奶量列出来,可以构成一个n*T的矩阵,其中n表示奶牛数量,T表示最大温度。就像下面的矩阵一样。 X
2022-01-13 青椒
  目录