Part1 问题
Part2 答案
Part1
1.进程和线程的区别
2.线程独立拥有哪些资源?
3.进程的内存空间如何划分?
4.并行和并发的区别
5.进程间的通信方式有哪些?
6.线程间的通信方式有哪些?
7.虚拟内存是什么?优点是什么?
8.堆和栈的区别是什么?
9.中断是什么?缺页中断是什么?
10.虚拟内存的页面置换算法有哪些?
11.大端和小端是什么?
12.怎么判断系统是大端还是小端?
13.大端的优点是什么?小端的优点是什么?
14.C++中哪些容器是线程安全的?
15.多线程的同步方式有哪些?
16.用户态是什么?内核态是什么?
17.用户态和内核态的区别是什么?
18.用户态和内核态如何相互切换?
19.死锁产生的原因是什么?
20.死锁的四个必要条件?
21.fork()的功能是什么?
22.系统调用是什么?有哪些常见的系统调用?
23.共享内存为什么效率高?
24.fork()的写时复制是什么?
25.孤儿进程是什么? 如何解决孤儿进程带来的问题?
26.僵尸进程是什么? 如何解决僵尸进程带来的问题?
27.线程需要保存哪些上下文?
28.游戏服务器应该为每个用户开辟一个线程还是进程?
29.为什么要字节对齐?
30.字节对齐的规则是什么?
31.如何定义结构体对齐?
32.静态链接和动态链接的区别是什么?
33.如何利用ps命令找到孤儿进程和僵尸进程?
34.僵尸进程的危害是什么?
Part2
1.进程和线程的区别
- 进程是操作系统资源分配的基本单位,线程是CPU任务调度和分配的基本单位。
- 进程有独立的地址空间和资源。线程没有独立的地址空间和资源,共享进程的地址空间和资源。
- 进程切换开销大,速度慢。线程切换开销小,速度快。
- 一个进程奔溃不会影响其他进程。一个线程奔溃,整个进程都会结束,会影响到其他线程。所以多进程比多线程健壮。
2.线程独立拥有哪些资源?
程序计数器PC,栈,寄存器
3.进程的内存空间如何划分?
代码区:程序编译后的可执行机器指令
堆区:用new或者malloc申请的动态内存
数据区:全局变量
4.并行和并发的区别
并行:当前任务数小于处理器个数,多个处理器上同时运行不同的任务。多个任务同时执行。
并发:当前任务数大于处理器个数,每个处理器用时间片轮转算法在不同时刻运行不同的任务。多个任务交替执行。
5.进程间的通信方式有哪些?
匿名管道:
- 半双工(一个进程只能读,另一个进程只能写)
- 只能用于有共同祖先的进程之间
- 面向字节流
有名管道:性质与匿名管道类似,但是可以用于任意两个进程之间
信号量:信号量是一个计数器,作为一种锁机制,防止多个进程同时访问一个资源。
消息队列:
- 全双工
- 面向数据报
- 用于任意进程之间
信号:模拟中断机制,通知进程某个件事发生了。
共享内存:
- 进程映射了一块其他进程也可以访问的内存。
- 这块内存由一个进程创建,但是多个进程都可以访问。
- 速度最快的进程间通信方式,因为内存可以随机访问。
套接字:用于不同设备间的进程通信。
6.线程间的通信方式有哪些?
锁机制:
- 互斥锁:以排他的方式防止数据被并发修改
- 读写锁:多个进程可以同时读共享数据,但是写操作是互斥的
- 条件变量:以原子的方式阻塞进程,直到某个条件为真
信号量机制
信号机制
7.虚拟内存是什么?优点是什么?缺点是什么?
虚拟内存给每个进程提供了一块连续完整的内存,实际上虚拟内存被映射到多个物理内存中碎片中。
优点:
- 扩大地址空间
- 内存保护:每个进程运行在自己的虚拟内存地址空间,保证了每个进程的地址空间不会被其他进程破坏
- 公平内存分配:为每个进程提供了一致的内存空间
缺点:
- 新增数据结构,占用额外内存
- 页面换入换出需要进程磁盘I/O操作,很耗时
- 虚拟内存地址转换到物理内存地址,增加了指令执行时间
- 数据在多个页面中,浪费内存
8.堆和栈的区别是什么?
堆 | 栈 |
程序员手动申请和释放 | 编译器管理 |
有可能发生内存泄漏 | 不会发生内存泄漏 |
可申请的堆的大小为虚拟内存的大小 | 1MB(Windows)10MB(Linux) |
内存地址由低到高 | 内存地址由高到低 |
只能动态分配 | 可以静态分配,也可也动态分配(使用alloca函数申请栈内存,无需手动释放) |
效率低(造成大量内存碎片) | 效率高 |
存放程序员指定的内容 | 存放函数返回地址、参数、局部变量、寄存器 |
9.中断是什么?缺页中断是什么?
中断指的是硬件或者软件发出的信号,用于暂停正在执行的的进程并请求操作系统处理某个事件。
外中断:外部事件引发的中断,与当前运行的进程无关
内中断:异常,由CPU内部事件引发的中断,比如程序中断(除0,地址越界),CPU会暂停当前进程,区执行异常对应的处理程序。
缺页中断指的是内存中没有找到当前要访问的页面,需要访问磁盘找到该页面,调入内存中。
缺页中断处理步骤:
保存CPU现场
- 分析中断原因
- 转入中断处理程序进行处理
- 恢复CPU线程、继续执行
缺页中断与一般中断的区别:
- 在指令执行期间产生和处理中断
- 一条指令在执行期间可能产生多次缺页中断
- 返回当前执行的指令,而不是下一条指令
10.虚拟内存的页面置换算法有哪些?
最佳置换算法:淘汰永不使用或者在未来最久才被使用的页面
先进先出置换算法:淘汰最先调入内存的页面
最近最近未被使用算法(LRU):淘汰最近一段时间最久未被使用的页面
最不经常使用算法:淘汰最近一段时间被使用次数最低的页面
11.大端和小端是什么?
大端指的是高位字节存储在低地址,低位字节存储在高地址。
小端指的是低位字节存储在低地址,高位字节存储在高地址。
12.怎么判断系统是大端还是小端?
利用union,给int赋值,然后去取char的值,看得到的是int的高位还是低位。如果得到的是int的高位,那么系统是大端,否则是小端。
13.大端的优点是什么?小端的优点是什么?
大端的优点:第一位就是符号位,可以快速判断数字正负
小端的优点:
- 强制类型转换时可以直接取出低地址的低位字节(1,2,4字节的存储方式都相同)
- CPU的计算是从低位到高位的,使用小端时,从低地址到高地址正好是数据的低位到高位,计算高效
14.C++中哪些容器是线程安全的?
没有容器是线程安全的。
STL函数库不提供任何强度的线程安全保证。
15.多线程的同步方式有哪些?
互斥锁
- 使用互斥锁可以实现堆共享资源的互斥访问。访问前加锁,访问后解锁。保证同一时间只要一个线程访问共享资源。
读写锁
- 读写锁将操作分为读操作和写操作。允许多个线程同时进行读操作。
- 写独占:当有线程占用写锁时,无论其他线程要加读锁还是写锁,都会被阻塞。
- 读共享:当有线程占用读锁时,其他线程加写锁会阻塞,加读锁会成功。
- 两种读写锁策略:
- 强读优先:读锁优先,只要写锁没有占用,就可以加读锁。
- 强写优先:写锁优先,只有没有写锁占用或者没有写锁在等待中,才可以加读锁
条件变量
- 条件变量本质上是一个多个线程共享的全局变量,用于阻塞线程,被阻塞的线程直到收到条件为真的信号才能继续执行。
信号量
- 信号量本质上是一个非负的整数计数器,用于控制公共资源的访问,用P/V原子操作来完成。
- P操作:若信号量大于一,则减一,否则阻塞线程
- V操作:信号量加一,若有线程阻塞,则唤醒线程
- 信号量允许多个线程进入临界区,而互斥量只允许一个线程进入临界区
16.用户态是什么?内核态是什么?
用户态:运行用户进程,只能执行非特权指令。
内核态:运行操作系统程序,可以执行特权指令
17.用户态和内核态的区别是什么?
用户态下的进程只能执行非特权指令,访问有限的内存空间,CPU可以被抢占
内核态下的进程能执行所有指令,访问所有的内存空间,CPU不能被抢占
18.用户态和内核态如何相互切换?
用户态切换内核态:系统调用、中断、异常
系统调用:用户态下的进行主动请求操作系统提供的的需要更高权限的服务
中断:外围设备完成用户请求的操作后,会向CPU发送中断信号,CPU会暂停执行下一条指令,先去执行中断对应的中断处理程序
异常:用户态的进程内部出现异常,触发CPU切换到内核态执行对应的异常处理程序
内核态切换用户态:设置程序状态字PSW
19.死锁产生的原因是什么?
资源分配不当
进程运行推进的顺序不当
资源有限
20.死锁的四个必要条件?
互斥:一个资源每次只能被一个进程使用
请求并保持:当一个进程请求资源而被阻塞,不会释放已经获取的资源
不剥夺条件:进程已获得的资源,在未使用完之前,不强行剥夺
循环等待环路:请求资源的进程形成首尾相接的循环等待资源的关系
21.fork()的功能是什么?
创建一个与原进程几乎一样的子进程。系统先给子进程分配资源,例如存储数据和代码的空间,然后将原进程的所有数据都复制到子进程中,只用少数数据不一样。
子进程从fork()的下一句指令开始执行。
- 在父进程中fork()返回子进程的PID。
- 在子进程中fork()返回0。
父进程和子进程并发执行,谁先执行不一定,与CPU调度算法有关。
22.系统调用是什么?有哪些常见的系统调用?
系统调用指的是在用户态运行的进程请求操作系统提高更高权限的服务。
常见的系统调用:
fork()、文件读写、文件系统操作、系统控制、内存管理、Socket套接字
23.共享内存为什么效率高?
因为管道和消息队列都需要在进程间复制一遍数据,而共享内存不需要复制,多个进程直接访问同一块内存。
24.fork()的写时复制是什么?
fork()产生新进程的速度非常快,因为子进程并不会立刻复制一份原进程的内存空间,而是和原进程共享原进程的内存空间。
这块内存空间的特性是“写时复制”。原进程和子进程都可以同时、自由地读取这块共享内存空间,直到原进程或者子进程要修改这块内存空间,才会为该进程复制一份内存空间。以免影响到使用这块内存空间的其他进程。
25.孤儿进程是什么? 如何解决孤儿进程带来的问题?
当父进程已经退出,子进程还没有退出。子进程就成为了孤儿进程。
PID为1的init进程会接管孤儿进程,负责在孤儿进程退出后,释放对应的资源。防止孤儿进程一直占用资源。
26.僵尸进程是什么? 如何解决僵尸进程带来的问题?
子进程已经退出了,父进程没有及时释放子进程占用的资源。这时子进程被称为僵尸进程,因为僵尸进程已经退出,却仍然占用系统的进程表项和一些资源。
如何解决:
使僵尸进程的父进程退出,这样init进程会默认接管僵尸进程,释放资源。
子进程退出前向父进程发出SIGCHLD信号,父进程收到信号后使用wait或者waitpid释放资源。
fork()两次:父进程先fork出子进程,子进程再fork出一个孙子进程,子进程再退出。这样孙子进程默认由init进程接管,负责释放资源。
27.线程需要保存哪些上下文?
当前线程id、线程状态、堆栈、寄存器状态等。
寄存器包括:
SP:堆栈指针,指向当前栈的栈顶位置
PC:程序计数器,存储下一条将要执行的指令
EAX:累加寄存器,用于加法乘法的缺省寄存器
28.游戏服务器应该为每个用户开辟一个线程还是进程?
开辟进程。因为每个线程没有自己独立的内存空间,且一个线程奔溃后会影响到其他线程。
29.为什么要字节对齐?
移植原因:有些硬件平台不能访问任意地址上的任意数据,只能访问某些特定地址上的特定类型的数据。
性能原因:提升访问效率,未对齐的内存可能会出现访问一个数据要访问两次内存的情况
30.字节对齐的规则是什么?
对于每个数据成员,取min(数据成员的大小,pragma pack(n) 中定义的n),数据成员的起始地址需要是这个数的倍数。
对于结构体中嵌套包含的结构体数据成员,取min(结构体数据成员的中最大的数据成员大小,pragma pack(n) 中定义的n),
对于整个结构体,取min(最大的数据成员的大小,pragma pack n中定义的n),结构体的大小需要是这个数的倍数。
31.如何定义结构体对齐?
使用 预编译指令 pragma pack(n) 设置
32.静态链接和动态链接的区别是什么?
静态链接是在编译后进行链接,连接器从库中复制函数与程序的其他模块组合,生成一个可执行文件。
静态链接的缺点:
空间浪费:如果每个程序都用到了同一个目标文件,那么每个可执行文件中都会有该目标文件的副本,造成空间浪费。
更新困难:每当更新了库函数,就需要重新编译链接形成可执行文件。
静态链接的优点:
运行速度快:每当运行时,就可执行程序已经具备了运行程序所需的所有内容,执行速度快。
动态链接是按照模块将程序分成多个独立的部分,在运行时链接起来,形成一个完整的程序。而不是像静态链接那样生成一个单独的可执行文件。
动态链接的缺点:
执行速度慢,每次执行都需要先链接。
动态链接的优点:
节省空间:即使多个程序依赖同一个目标文件,也只需要目标文件的一个副本,多个程序再执行时共享同一个目标文件,不会造成空间浪费。
更新方便:更新库函数后,不需要再次编译链接每一个程序。当程序下一次运行,会自动链接新版的目标函数。
33.如何利用ps命令找到孤儿进程和僵尸进程?
孤儿进程的父进程ID为1。
僵尸进程的状态为Z。
34.僵尸进程的危害是什么?
占用系统资源,例如占用进程号。
影响系统性能:过多的僵尸进程会导致进程表项等数据结构过大,影响系统性能。
安全漏洞:恶意攻击者可以创建大量僵尸进程,以消耗系统资源