大家好,又见面了,我是你们的朋友全栈君。
题目一:
使用下图所示的程序,说明LINE A的输出是什么。为什么?
解答:
我自己思考的是首先定义了一个名为value的变量,初始值为5,然后进入main程序,首先创建了一个子进程,然后进入if判断,这个时候有两个进程,分别进行判断。对于子进程,会执行value+=15,但由于两个进程共享代码空间,而数据空间是独立的,所以子进程对value的改变不会影响到父进程中的value。子进程执行完毕,回到父进程,会打印出PARENT:value=5,所以LINE A为PARENT:value=5
但我在计算机上进行执行的时候,发现代码本身有问题:
这里进行修改后(将exit进行注释掉),得到的line如下:
这里我对父进程和子进程的执行顺序有所疑问,所以在pid==0的分支(子进程分支里)增添了一个输出语句,用于查看是子进程先执行还是父进程先执行:
可以看出,似乎虽然父子进程是同级的,但还是子进程先执行,父进程后执行
但是,当我在原本的pid=0分支(子进程分支里)再增添一个pid=fork之后,发现又出现了奇怪的事情:
原本按照子先父后的规律,应该是先创建子进程1,在子进程1中又创建子进程2,然后先执行子进程2,打印value=25,然后执行子进程1,打印value=20,最后执行父进程,打印value=5,但实际上是这样的:
所以,最终的结论是,父子进程是同级的,不管是多少级的父子进程,仍旧是同级别,而且在cpu执行的时候是并行进行的。这一点在我又加入了一个子进程的分支之后得到了验证,可以看到即便是同一个程序,两次执行,输出的顺序是不一样的:
题目二:
下面设计的优点和缺点分别是什么?分别从操作系统层面和用户层面来阐述。
• 同步和异步通信
• 自动和显式缓冲
• 复制传送和引用传送
• 固定大小和可变大小消息
解答:
• 同步和异步通信:
所谓同步通信,就是在发出一个功能调用时,在没有得到结果之前,该调用的就不返回。在系统层次,同步通信会因为等待反馈信息而一直占用内存和系统资源,这是他的缺点;但在用户层次,同步通信保证了用户之间的同时同步性,确保了用户体验,这是他的优点。
所谓异步通信,就是在发出一个功能调用后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。在系统层次,使用异步通信的优点是可以使系统尽可能高效率的进行不同的对象进行通信;在用户层次,缺点是在发出信息后不能立刻得到结果,甚至可能因为某种错误的发生而不能得到结果,影响用户体验。
• 自动和显式缓冲:
自动缓冲包括有限容量和无限容量的消息系统。在有限容量下,队列的长度为有限的n;因此最多只能有n个消息主流其中,如果在发送新信息的时候队列未满,那么该消息可以放在队列中,且发送者可以继续执行而不必等待。不过,线路容量有限,如果线路满,必须阻塞发送者直到队列中的空间可用位置。对于无限容量来说,队列长度可以无限,因此不管有多少消息都可以在其中等待,从不阻塞发送者。所以对于用户层次来说,采用自动缓冲可以流畅的发送信息而不用担心被阻塞而造成的卡顿情况,这是他的优点;而在系统层次,这样做会消耗或者浪费大量的系统资源和内存空间,这是他的缺点。
采用显示缓冲可能使用户在发送信息时被阻塞而等待一段时间,这是他的缺点。但在系统层面,这样只会使用一小部分内存空间,避免了系统资源的浪费,这是他的优点。
• 复制传送和引用传送:
复制发送不允许接收者改变参数的状态,所以保证了参数的不可改变性,这样对于系统来说可以保持通信传输之间的一致性,这是他的优点;但对于用户来说,不能够改变参数的状态会造成一些不便,这是他的缺点。
而引用发送允许改变参数的状态,由此他的优点之一是它允许程序员写一个分布式版本的一个集中的应用程序。
• 固定大小和可变大小消息:
由进程发送的消息可以是定长的或变长的。如果只能发送定长消息,那么系统级的实现十分简单,不过这一限制让编程任务更加困难。相反的,变长消息要求更复杂的系统级实现,但是编程任务变得更简单。
题目三:
fibonacci序列是一组数:0,1,1,2,3,5,8,…,通常他可以表示为:
使用系统调用fork()编写一个c程序,使其在子程序中生成fibonacci序列,序列的号吗将在命令行中提供。例如,如果提供的是5,fibonacci序列中的前5个数将由子进程输出。退出程序前,父进程调用wait()调用来等待子进程结束。执行必要的错误检查以保证不会接受命令行传递来的负数号码。
解答:
拿到这个题,我的第一反应是“明明子进程和父进程的数据空间是独立的,如何使用子进程来实现有联系的fibonacci数列呢?”,后来想到,实际上我不需要每次输出一个fibonacci数就要产生一个新的子进程,可以只产生一个子进程,而在子进程中用循环产生即可。
具体代码如下:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
pid_t pid;
int i;
int f0,f1,f2;
f0=0;
f1=1;
if(argv[1]<0)
printf("requer a number");
pid=fork();
if(pid<0){
printf("need a nun-negative number");
exit(-1);
}else if(pid == 0){
printf("%d\n",atoi(argv[1]));
printf("0 1 ");
for(i=2;i<atoi(argv[1]);i++){
f2=f0+f1;
f0=f1;
f1=f2;
printf("%d ",f2);
}
}else {
wait(NULL);
printf("end");
}
return 0;
}
结果如下:
题目四:
请检索文献了解某一特定操作系统(如 Solaris,Windows 等)所提供的进程状态及其可能的状态转换关系,并与基本的进程状态转换图进行比较。
解答:
这是linux系统的进程状态图,可以看到与基本的进程状态转换图基本一致,其中就绪状态没有改变;创建状态没有体现;运行状态没有改变,对应于占有cpu执行状态;阻塞状态分成了暂停,深度睡眠,浅度睡眠三个状态;停止状态对应于死亡状态
题目五:
请详细描述上下文的切换过程,并谈谈你对上下文切换的作用理解。
解答:
上下文切换(有时也称做进程切换或任务切换)是指CPU从一个进程或线程切换到另一个进程或线程稍微详细描述一下,上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:
挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。因此上下文是指某一时间点CPU寄存器和程序计数器的内容, 广义上还包括内存中进程的虚拟地址映射信息。
上下文切换只能发生在内核态中, 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少
个人理解是:上下文是指某一时间点cpu寄存器和程序计数器的内容。上下文切换是指cpu从一个进程或线程切换到另一个进程或者线程,可以认为是内核在cpu上对进程进行以下活动:
1、挂起一个进程,将这个进程在cpu中的状态存储在内存中的某处
2、在内存中检索下一个进程的上下文并将其在cpu中的寄存器中恢复
3、跳转到程序计数器所指向的位置,以恢复该进程
题目六:
请简述你对进程的理解,并分析进程与程序的区别和联系。
解答:
想要理解进程,需要从进程的三个方面进行理解,即:映像、上下文/状态、内核数据结构(PCB)
1、映像:进程是可执行程序的映像,包含代码(文本段)、当前活动(程序计数器的值与寄存器的内容)、进程堆栈段(临时数据)、数据段(全局变量)、堆(动态分配的内存)。
2、上下文/状态:进程状态有五种,分别是新建、运行、等待、就绪、终止。
3、进程控制块(PCB):每个进程在os内用进程控制块表示,其中包含进程状态(以上)、程序计数器、cpu寄存器、cpu调度信息、内存管理信息、记账信息、I/O状态信息
进程与程序的区别:程序是被动实体,进程为活动实体(有一个程序计数器来表示下一个要执行的命令与相关资源集合),同一个程序可以产生多个进程。只有当一个可执行文件被装入内存,程序才能成为进程。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159187.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...