admin管理员组文章数量:1122853
文章目录
-
-
-
- 1.13 什么是协程?
- 1.14 为什么协程比线程切换的开销小?
- 1.15 线程和进程的区别?
- 1.16 进程切换为什么比线程更消耗资源?
- 1.17 介绍一下进程之间的通信。
- 1.18 介绍一下信号量。
- 1.19 说说僵尸进程和孤儿进程。
- 1.20 请介绍进程之间的通信方式。
- 1.21 请介绍线程之间的通信方式。
- 1.22 说一说进程的状态。
- 1.23 CPU调度的最小单位是什么?线程需要CPU调度吗?
- 1.24 进程之间共享内存的通信方式有什么好处?
- 1.25 如何杀死一个进程?
- 1.26 说一说kill的原理。
- 1.27 介绍下你知道的锁。
- 1.28 什么情况下会产生死锁?
- 1.29 说一说你对自旋锁的理解。
- 1.30 说一说你对悲观锁的理解。
- 1.31 说一说你对乐观锁的理解。
- 1.32 CAS在什么地方用到过吗?
- 1.33 谈谈IO多路复用。
- 1.34 谈谈poll和epoll的区别。
- 1.35 谈谈select和epoll的区别。
- 1.36 epoll有哪两种模式?
- 1.37 说一下epoll的原理,它的查询速度是O(1)的吗?
- 1.38 介绍域名解析成IP的全过程。
- 1.39 如何在Linux上配置一个IP地址,如果给定端口号如何解析出域名?
- 1.40 解释一下IP地址、子网掩码、网关。
- 1.41 说说IP如何寻址?
- 1.42 操作系统的地址有几种,请具体说明。
- 1.43 Linux的静态网络怎么配置?
- 1.44 DNS用了哪些协议?
- 1.45 说一说你对Linux内核的了解。
- 1.46 说一说你对Linux内核态与用户态的了解。
- 1.47 Linux负载是什么?
- 1.48 Linux如何设置开机启动?
- 1.49 谈谈Linux的内存管理。
- 1.50 谈谈内存映射文件。
- 1.51 谈谈虚拟内存模型。
- 1.52 什么是物理内存和虚拟内存,为什么要有虚拟内存?
- 1.53 内存和缓存有什么区别?
- 1.54 请你说说缓存溢出。
- 1.55 深拷贝和浅拷贝的区别是什么,它们各自的使用场景是什么?
- 1.56 说说IO模型。
- 1.57 Linux中的软链接和硬链接有什么区别?
- 1.58 说说缺页中断机制。
- 1.59 软中断和硬中断有什么区别?
- 1.60 介绍一下你对CopyOnWrite的了解。
- 1.61 Linux替换文本该如何操作呢?
- 1.61 Linux替换文本该如何操作呢?
-
-
1.13 什么是协程?
参考回答
协程:协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
答案解析
-
线程与协程的区别:
(1)协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
(2)协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高。
(3)一个线程可以有多个协程。
-
协程的优势:
(1)协程调用跟切换比线程效率高:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
(2)协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
(3)切换开销更少:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。
1.14 为什么协程比线程切换的开销小?
参考回答
(1)协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
(2)协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高。避免了加锁解锁的开销。
1.15 线程和进程的区别?
参考回答
(1)一个线程从属于一个进程;一个进程可以包含多个线程。
(2)一个线程挂掉,对应的进程挂掉;一个进程挂掉,不会影响其他进程。
(3)进程是系统资源调度的最小单位;线程CPU调度的最小单位。
(4)进程系统开销显著大于线程开销;线程需要的系统资源更少。
(5)进程在执行时拥有独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。
(6)进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈,线程切换时只需要切换硬件上下文和内核栈。
(7)通信方式不一样。
(8)进程适应于多核、多机分布;线程适用于多核
1.16 进程切换为什么比线程更消耗资源?
参考回答
进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;线程切换时只需要切换硬件上下文和内核栈。
答案解析
进程是程序的动态表现。 一个程序进行起来后,会使用很多资源,比如使用寄存器,内存,文件等。每当切换进程时,必须要考虑保存当前进程的状态。状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开的文件描述符的集合,这个状态叫做上下文(Context)。可见,想要切换进程,保存的状态还不少。不仅如此,由于虚拟内存机制,进程切换时需要刷新TLB并获取新的地址空间。
线程存在于进程中,一个进程可以有一个或多个线程。线程是运行在进程上下文中的逻辑流,这个线程可以独立完成一项任务。同样线程有自己的上下文,包括唯一的整数线程ID, 栈、栈指针、程序计数器、通用目的寄存器和条件码。可以理解为线程上下文是进程上下文的子集。
由于保存线程的上下文明显比进程的上下文小,因此系统切换线程时,必然开销更小。
1.17 介绍一下进程之间的通信。
参考回答
为了提高计算机系统的效率.增强计算机系统内各种硬件的并行操作能力.操作系统要求程序结构必须适应并发处理的需要.为此引入了进程的概念。而进程并行时,需要考虑进程间的通信,进程间通信主要有以下几种方式:匿名管道、命名管道、信号、消息队列、共享内存、信号量、Socket。
- 匿名管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int pipe_default[2];
int main()
{
pid_t pid;
char buffer[32];
memset(buffer, 0, 32);
if(pipe(pipe_default) < 0)
{
printf("Failed to create pipe!\n");
return 0;
}
if(0 == (pid = fork()))
{
close(pipe_default[1]); //关闭写端
sleep(2);
if(read(pipe_default[0], buffer, 32) > 0)
{
printf("[Client] Receive data from server: %s \n", buffer);
}
close(pipe_default[0]);
}
else
{
close(pipe_default[0]); //关闭读端
char msg[32]="== hello world ==";
if(-1 != write(pipe_default[1], msg, strlen(msg)))
{
printf("[Server] Send data to client: %s \n",msg);
}
close(pipe_default[1]);
waitpid(pid, NULL, 0);
}
return 1;
}
-
有名管道
匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO)。
有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信,因此,通过有名管道不相关的进程也能交换数据。值的注意的是,有名管道严格遵循先进先出(first in first out) ,对匿名管道及有名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。有名管道的名字存在于文件系统中,内容存放在内存中。
-
信号
- 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
- 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,知道该进程回复执行并传递给它为止。
- 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。
以下列出几个常用的信号:
信号 | 描述 |
---|---|
SIGHUP | 当用户退出终端时,由该终端开启的所有进程都退接收到这个信号,默认动作为终止进程。 |
SIGINT | 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C )时发出,用于通知前台进程组终止进程。 |
SIGQUIT | 和SIGINT 类似, 但由QUIT字符(通常是Ctrl+\ )来控制. 进程在因收到SIGQUIT 退出时会产生core 文件, 在这个意义上类似于一个程序错误信号。 |
SIGKILL | 用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。 |
SIGTERM | 程序结束(terminate)信号, 与SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。 |
SIGSTOP | 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略. |
代码示例:
下面的代码收到程序退出信号后会执行用户定义的信号处理函数来替代系统默认的处理程序。
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void sig_handle(int sig) {
printf("received signal: %d, quit.\n", sig);
exit(0);
}
int main () {
signal(SIGINT, sig_handle);
signal(SIGKILL, sig_handle);
signal(SIGSEGV, sig_handle);
signal(SIGTERM, sig_handle);
int i = 0;
while (1) {
printf("%d\n", ++i);
sleep(2);
}
printf("main quit.");
return 0;
}
运行结果:
1
2
received signal: 15, quit.
- 消息队列
- 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。
- 与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
- 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。
消息队列特点总结:
(1)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.
(2)消息队列允许一个或多个进程向它写入与读取消息.
(3)管道和消息队列的通信数据都是先进先出的原则。
(4)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。
(5)消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
(6)目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
-
共享内存
进程间本身的内存是相互隔离的,而共享内存机制相当于给两个进程开辟了一块二者均可访问的内存空间,这时,两个进程便可以共享一些数据了。但是,多进程同时占用资源会带来一些意料之外的情况,这时,我们往往会采用上述的信号量来控制多个进程对共享内存空间的访问。
#include <iostream> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h> using namespace std; int main() { char *shmaddr; char *shmaddread; char str[]="Hello, I am a processing. \n"; int shmid; key_t key = ftok(".",1); pid_t pid1 = fork(); if(pid1 == -1){ cout << "Fork error. " << endl; exit(1); } else if(pid1 == 0){ //子进程 shmid = shmget(key,1024,IPC_CREAT | 0600); shmaddr = (char*)shmat(shmid, NULL, 0); strcpy(shmaddr, str); cout << "[Writer] write: " << shmaddr << endl; shmdt(shmaddr); } else { //父进程 pid_t pid2 = fork(); if(pid2 == -1){ cout << "Fork error. " << endl; exit(1); } else if(pid2 == 0){ //子进程 sleep(2); shmid = shmget(key,1024,IPC_CREAT | 0600); shmaddread = (char*)shmat(shmid, NULL, 0); cout << "[Reader] read: " << shmaddread << endl; shmdt(shmaddread); } } sleep(3); return 0; }
-
信号量
信号量主要用来解决进程和线程间并发执行时的同步问题,进程同步是并发进程为了完成共同任务采用某个条件来协调他们的活动,这是进程之间发生的一种直接制约关系。
对信号量的操作分为P操作和V操作,P操作是将信号量的值减一,V操作是将信号量的值加一。当信号量的值小于等于0之后,再进行P操作时,当前进程或线程会被阻塞,直到另一个进程或线程执行了V操作将信号量的值增加到大于0之时。锁也是用的这种原理实现的。
信号量我们需要定义信号量的数量,设定初始值,以及决定何时进行PV操作。
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/sem.h> #define KEY (key_t)15030110070 #define N 20 static void p(int semid ,int semNum); static void v(int semid ,int semNum); union semun { int val; struct semid_ds *buf; ushort *array; }; int main(int argc ,char* argv[]) { int i; int semid; semid = semget(KEY,3,IPC_CREAT|0660); union semun arg[3]; arg[0].val = 1; //mutex [0] 对缓冲区进行操作的互斥信号量 arg[1].val = N; //empty [1] 缓冲区空位个数n arg[2].val = 0; //full [2] 产品个数 for(i=0;i<3;i++) semctl(semid,i,SETVAL,arg[i]); pid_t p1,p2; if((p1=fork()) == 0) { //子进程1,消费者 while(1) { printf("消费者 1 等待中...\n"); sleep(2); int product = rand() % 2 + 1; for(int i = 0; i < product; i++) { p(semid ,2); //消费 p(semid ,0); //加锁 printf(" [消费者 1] 消费产品 1. 剩余: %d\n", semctl(semid, 2, GETVAL, NULL)); v(semid ,0); //开锁 v(semid ,1); //释放空位 } sleep(2); } } else { if((p2=fork()) == 0) { //子进程2,消费者 while(1) { printf("消费者 2 等待中...\n"); sleep(2); int product = rand() % 2 + 1; for(int i = 0; i < product; i++) { p(semid ,2); //消费 p(semid ,0); //加锁 printf(" [消费者 2] 消费产品 1. 剩余: %d\n", semctl(semid, 2, GETVAL, NULL)); v(semid ,0); //开锁 v(semid ,1); //释放空位 } sleep(2); } } else { //父进程,生产者 while(1) { printf("生产者开始生产...\n"); int product = rand() % 5 + 1; for(int i = 0; i < product; i++) { p(semid ,1
版权声明:本文标题:操作系统面试题目详解 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1726362203a1080907.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论