操作系统-科普-1

——来源《操作系统导论》,感兴趣可以直接看书

进程API

进程管理是操作系统非常非常重要的一个部分。那么:

操作系统应该提供怎样的进程来创建及控制接口 ? 如何设计这些接口才能既方便又实用?

fork()系统调用

int main(int argc, char * argv[])
{
	printf("hellooworld (pid:%d)\n", (int) getptd();
    int rc = fork();
 	if(rc < 0){
		fprintf(stderr, "fork faild");
	}else if(rc == 0 ){
		printf("我是个孩子(pid:%d)\n", (int) getptd();
	}else{
		printf("我是个 (pid:%d) 的爸爸(pid:%d)\n", rc, (int) getptd();
	}
	return 0;
}

这段程序的输出是什么?

prompt> ./p1

hello world(pid:29146)

hello, I am parent of 29147 (pid:29146)

hello, I am child (pid:29147)

prompt>

会有其他的输出结果吗?

科普解释

进程调用了fork()系统调用,用来创建新进程。新旧进程几乎一模一样,且新就进程都会从fork()系统调用中返回。

新创建的进程称为子进程(child),原来的进程称为父进程(parent)。

子进程不会从main()函数开始执行( 因此hello world 信息只输出了一次),而是直接从fork()系统调用返回,就好像是它自己调用了fork()。

但是父子进程从fork()返回的返回值是不一样的。

父进程获得的返回值是新创建子进程的PID,而子进程获得的返回值是0。这个差别非常重要,因为这样就很容易编写代码处理两种不同的情况(像上面那样)。

子进程并不是完全拷贝了父进程。我们可以观察到父子进程的pid是不一致的。子进程也有自己独立的地址空间,有内存堆栈、有寄存器、有程序计数器。

wait系统调用

int main(int argc, char * argv[])
{
	printf("hellooworld (pid:%d)\n", (int) getptd());
    int rc = fork();
 	if(rc < 0){
		fprintf(stderr, "fork faild");
	}else if(rc == 0 ){
		printf("我是个孩子(pid:%d)\n", (int) getptd());
	}else{
		int wc = wait(NULL);
		printf("我是个 (pid:%d) 的爸爸(pid:%d)\n", rc, (int) getptd());
	}
	return 0;
}

通过wait()方法,我们的输出结果就是固定的了。尽管,仍然哟可能父进程先被调用。

exec()系统调用

我们的第三个程序p3.c

int main(int argc, char * argv[])
{
	printf("hellooworld (pid:%d)\n", (int) getptd();
    int rc = fork();
 	if(rc < 0){
		fprintf(stderr, "fork faild");
	}else if(rc == 0 ){
		printf("hello, I am child (pid:8d)\n", (int) getpid());
		char *myargs[3];
		myargs[0] = strdup("wc"); // program: wc是用来统计文件的字符个数的一个程序
		myargs[1] = strdup("p3.c"); // p3.c是需要被统计的文件
		myargs[2] = NULL;
		execvp (myargs[0], myargs) ; // 
		printf ("我是个孩子(pid:%d)\n", (int) getptd()) ;
	}else{
		int wc = wait(NULL);
		printf("我是个 (pid:%d) 的爸爸(pid:%d)\n", rc, (int) getptd();
	}
	return 0;
}

这段代码的调用结果是什么?会输出“我是孩子吗”?

科普解释

fork()系统调用很奇怪,它的伙伴execp()也不一般。 给定可执行程序的名称(如wc)、需要的参数(如p3.c)后,execvp会从可执行程序中加载代码和静态数据,并用它覆写当前进程的自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。

然后操作系统就执行该程序,将参数通过argv传递给这个进程。

因此,它并没有创建新进程,而是直接将当前运行的程序 替换为新的运行程序.子进程执行execvp()之后,几乎就像p3.c 从未运行过一样。对exec()的成功调用永远不会返回

思考

为什么操作系统设计fork() , wait(), execv() 这三个接口呢?接口似乎也非常不符合常见思路。

实际上,在后续的shell的调用中,这种做法还挺好用的。

shell 是一个用户程序,它首先显示一个提示符(prompt), 然后等待用户输入。

我们可以向它输入一个命令(一个可执行程序的名称及需要的参数)。然后shell帮助我们找到对应的可执行程序,调用fork()来创建新的进程,并调用exec()的某个变体来执行这个可执行程序,然后wait命令等待进程执行完安好才能。子进程执行结束后,shell 会从wait()返回并再次输出一个提示符,等待用户输入下一条命令。

fork()和exec()的分离,让shell可以方便地实现很多有用的功能。比如: 

 

prompt>  wC  p3.c > newfile. txt

 

在上面的例子中,wc的输出结果被重定向到文件newfile.txt中。shell怎么实现重定向。只需要在exec()之前先关闭标准输出,然后打开文件newfile. txt即可。

Lampson 在他的著名论文《Hints for Computer SystemsDesign》中曾经说过:“做对事(Getitright)。 抽象和简化都不能替代做对事。”

有时你必须做正确的事,当你这样做时,总是好过其他方案。有许多方式来设计创建进程的API,但fork()和exec()的组合既简单又极其强大。

因此UNIX的设计师们因为Lampson经常“做对事”,所以我们就以他来命名这条定律。

受限执行

多任务并行执行是操作系统需要处理的一件事。在单CPU时代,利用时分共享技术来实现虚拟化CPU,来实现。

然而,在构建这样的虚拟化机制时存在一些挑战。

第一个是性能: 如何在不增加系统开销的情况下实现虚拟化?

第二个是控制权:如何有效地运行进程,同时保留对CPU的控制?

控制权对于操作系统尤为重要,因为操作系统负责资源管理。

如果没有控制权,一个进程可以简单地无限制运行并接管机器,或访问没有权限的信息,比如访问不属于自己的地址空间的内存。因此,在保持控制权的同时获得高性能,这是构建操作系统的主要挑战之一

 

操作系统必须以高性能的方式虚拟化CPU,同时保持对系统的控制。为此,需要硬件和操作系统支持。操作系统通常会明智地利用硬件支持,以便高效地实现其工作。

直接执行

如果一个进程的任何命令的执行都需要经过一层操作系统。那么就像我们业务调用链路中增加一个链路节点一样,会增加耗时。

那么为了让CPU不被浪费,做一些不必要意义的入栈和出栈,那么让程序直接运行在CPU上即可。称受限的直接执行(limited direct execution)

所以我们的程序运行大概是这个样子。

操作系统程序

在进程列表中创建进程条目(PCB)

分配内存

将程序代码加载到内存中

根据argc/argv设置程序栈

 

清除寄存器

执行call mian()方法

 
 

执行main()

从main中执行return

释放进程的内存

从进程列表中清除

 

 

如果是这个样子,可能会遇到两个问题

  1. 如果程序想执行某些首先的操作(比如访问磁盘、比如申请更多的CPU和内存), 那操作系统似乎没办法进行控制
  2. 如果程序想一直执行下去(山无棱、天地合、才会放弃CPU),那就像操作系统将自己的皇位禅让给了程序一样

问题一:受限制的操作

一个进程必须能够执行IO和其他一些受限制的操作,但又不能让进城完全控制系统。操作系统和硬件应该如何协作这一点?

为此,我们必须先明确哪些是受限制的操作。其次,应该提供一种机制,让这些操作能够被限制到。

所以我们就可以想到:让用户进程通过操作系统执行特权操作。

可以类比:支付系统的订单支付状态。订单的支付状态就类比磁盘、CPU等资源,支付系统就类比操作系统, 更改订单支付状态的功能类比硬件提供的能力。那么订单系统就不适合随意更改订单的支付状态,需要通过支付系统对外提供的“发起付款”“发起退款”等小心暴露的接口来完成。

那就是这样子的:

操作系统程序

在进程列表中创建进程条目(PCB)

分配内存

将程序代码加载到内存中

根据argc/argv设置程序栈

 

清除寄存器

执行 call mian()方法

 
 

执行main()

.....

调用系统调用做一些特权操作

处理系统调用 
 

从特权操作中返回

.....

从main中返回

释放进程的内存

从进程列表中清除

 

那程序是如何感知到这是一次特权操作呢?通过特殊的陷阱指令(trap)。该指令同时跳入内核并将提升到内核模式。

一旦进入内核, 系统就可以执行任何需要的特权操作(如果允许),从而完成调用进程想要所需的功能。

完成后,操作系统调用一个特殊的从陷阱返回(return-from-trap)指令,如你期望的那样,该指令返回到用户进程中,同时回到用户模式。

操作系统程序

在进程列表中创建进程条目(PCB)

分配内存

将程序代码加载到内存中

根据argc/argv设置程序栈

 

清除寄存器

执行 call mian()方法

 
 

执行main()

.....

调用系统调用做一些特权操作

处理陷阱

处理陷阱

从陷阱中返回

 
 

从特权操作中返回

.....

从main中返回

释放进程的内存

从进程列表中清除

 

这里有个小的细节问题:

操作系统在处理陷阱的时候,应该执行什么样的指令呢?

首先:不同进程程序调用的特权操作可能是一样的;

其次:特权操作可能会有一些维护和升级;

那么特权操作的指令一定不是维护在用户程序中的。

===========> 如果我们将特权操作的代码的位置公开,由用户进程指定代码位置,就可以满足上面的要求了

但是如果一个程序员很强很狡猾,他就可以指定跳过文件访问的权限代码,直接执行文件访问的操作。

===========> 启动时设置陷阱表(trap table)来实现。

当机器启动的时,它在特权(内核)模式下执行,因此可以根据需要自由配置机器硬件。

操作系统做的第一件事, 就是告诉硬件在发生某些异常事件时要运行哪些代码。例如,当发生硬盘中断,发生键盘中断或程序进行系统调用时,应该运行哪些代码?

操作系统通常通过某种特殊的指令,通知硬件这些陷阱处理程序的位置。

一且 硬件被通知,它就会记住这些处理程序的位置,直到下一次重新启动机器 ,并且硬件知道在发生系统调用和其他异常事件时要做什么(即跳转到哪段代码)。

也就是说:特权操作的程序是由硬件和操作系统维护的。

===========> next 执行指令来告诉硬件陷阱表的位置,也是非常强大的一个指定!

操作系统硬件程序
启动(内核模式)  
初始化陷阱表  
 记住系统调用处理程序的地址 
运行(内核模式)  

在进程列表中创建进程条目(PCB)

分配内存

将程序代码加载到内存中

根据argc/argv设置程序栈

用寄存器/程序计数器填充内核栈

从陷阱中返回

  

 

从内核栈恢复寄存器

转向用户模式

跳转到mian

 
  

执行main()

.....

调用系统调用陷入操作系统

 

将寄存器保存到内核栈

转向内核模式

跳到陷阱处理程序

 

处理陷阱

处理陷阱

从陷阱中返回

  
 

从内核栈恢复寄存器

转向用户模式

跳到陷阱之后的程序计数器

 
  

从特权操作中返回

.....

从main中返回

释放进程的内存

从进程列表中清除

  

类比:

我是一个山庄的庄主,山庄靠卖火锅底料养着。

但是我比较懒,想找人来当管事。但是又不想给这个人过多的权限。万一他把我钱卷走了咋办,万一他把火锅底料的秘方卖了咋办。

所以,我就自己负责这块。管事平时可以随意造,但是涉及到火锅底料部分,他就必须申请权限,由我来负责和火锅底料的研发团队对接。

问题二:在进程之间切换

操作系统能够中断一个进程的执行,并且开始另一个进程,想想是件非常简单的事情。

但是进程是直接运行在CPU上的,操作系统是没有运行的,那么它应该怎么控制呢? 

一个进程必须能够执行IO和其他一些受限制的操作,但又不能让进城完全控制系统。操作系统和硬件应该如何协作这一点?

协作方式:等待系统调用

过去有些系统是会假定:系统的进程会合理运行。及时一个进程的运行事件很长,也会定期放弃CPU,以便操作系统决定运行其他任务。

那么友好的进程可能会通过系统调用,将CPU的控制权转移给操作系统。如果程序执行了某些非法操作,也会将控制转移给操作系统。

但如果系统遇到的都是坏人呢?

非协作方式:操作系统控制

许多年前构建计算机系统的许多人都发现了:时钟中断(timer interrrupt)。时钟设备可以每隔几毫秒产生一次中断。产生中断时,当前运行的进程停止,操作系统中预先配置的中断处理程序会运行。

那么这个时候,操作系统就会重新获得CPU的控制权,因此可以做它想做的事:停止当前进程,并启动另 一个进程。(联想一下进程调度~

这么说来,感觉硬件的控制权限会更大。时钟设备可以随时打断一个程序的执行,那程序会受到影响吗?

因此:硬件需要为正在运行的程序保存足够的状态(上下文切换~),以便程序继续运行时(随后从陷阱返回指令)能够正确恢复执行。

这一组操作与上一节中的硬件在显式系统调用陷入内核时的行为非常相似,其中各种寄存器因此被保存(进入内核栈),因此从陷阱返回指令可以容易地恢复。

操作系统硬件程序 
启动(内核模式)   
初始化陷阱表   
 

记住系统调用处理程序的地址

记住时钟处理程序

  
启动中断时钟   
 

启动时钟

每隔X毫秒中断CPU

  
运行(内核模式)   
  

程序A运行中...

程序B
 

时钟中断

将寄存器A保存到内核栈A

转向内核模式

跳到陷阱处理程序

  

处理陷阱

调用switch()例程

将寄存器A保存到进程结构A

将进程结构B恢复到寄存器B

从陷阱中返回(进入B)

   
 

从内核栈B回恢复寄存器B

转向用户模式

跳到B的程序计数器

  
   进程B运行中......

思考

操作系统虚拟化CPU的关键底层机制,就是:受限直接执行。就是:让你想运行的程序在CPU上运行,但首先确保设置好硬件,以便在没有操作系统帮助的情况下,可以限制进程执行的操作

类比一下:

有宝宝的家庭,可能会注意锁好有危险东西的柜子、房间,并且掩盖电源插座,当这些都准备好的时候,可以让宝宝自由行动。

之前我们指出,在协作式抢占时,无限循环(以及类似行为)的唯一解决方案是重启( reboot)机器。虽然你可能会嘲笑这种粗暴的做法,但研究表明,重启(或在通常意义上说,重新开始运行一些软件)可能是构建强大系统的一个非常有用的工具。

具体来说,重新启动很有用,因为它让软件回到已知的状态,很可能是经过更多测试的状态。重新启动还可以回收旧的或泄露的资源(例如内存),否则这些资源可能很难处理。最后,重启很容易自动化。由于所有这些原因,在大规模集群互联网服务中,系统管理软件定期重启一些机器,重置它们并因此获得以上好处,这并不少见。

因此,下次重启时,要相信自己不是在进行某种丑陋的粗暴攻击。实际上,你正在使用经过时间考验的方法来改善计算机系统的行为。干得漂亮!

以下是对提供的参考资料的总结,按照要求结构化多个要点分条输出: 4G/5G无线网络优化与网规案例分析: NSA站点下终端掉4G问题:部分用户反馈NSA终端频繁掉4G,主要因终端主动发起SCGfail导致。分析显示,在信号较好的环境下,终端可能因节能、过热保护等原因主动释放连接。解决方案建议终端侧进行分析处理,尝试关闭节电开关等。 RSSI算法识别天馈遮挡:通过计算RSSI平均值及差值识别天馈遮挡,差值大于3dB则认定有遮挡。不同设备分组规则不同,如64T和32T。此方法可有效帮助现场人员识别因环境变化引起的网络问题。 5G 160M组网小区CA不生效:某5G站点开启100M+60M CA功能后,测试发现UE无法正常使用CA功能。问题原因在于CA频点集标识配置错误,修正后测试正常。 5G网络优化与策略: CCE映射方式优化:针对诺基亚站点覆盖农村区域,通过优化CCE资源映射方式(交织、非交织),提升RRC连接建立成功率和无线接通率。非交织方式相比交织方式有显著提升。 5G AAU两扇区组网:与三扇区组网相比,AAU两扇区组网在RSRP、SINR、下载速率和上传速率上表现不同,需根据具体场景选择适合的组网方式。 5G语音解决方案:包括沿用4G语音解决方案、EPS Fallback方案和VoNR方案。不同方案适用于不同的5G组网策略,如NSA和SA,并影响语音连续性和网络覆盖。 4G网络优化与资源利用: 4G室分设备利旧:面对4G网络投资压减与资源需求矛盾,提出利旧多维度调优策略,包括资源整合、统筹调配既有资源,以满足新增需求和提质增效。 宏站RRU设备1托N射灯:针对5G深度覆盖需求,研究使用宏站AAU结合1托N射灯方案,快速便捷地开通5G站点,提升深度覆盖能力。 基站与流程管理: 爱立信LTE基站邻区添加流程:未提供具体内容,但通常涉及邻区规划、参数配置、测试验证等步骤,以确保基站间顺畅切换和覆盖连续性。 网络规划与策略: 新高铁跨海大桥覆盖方案试点:虽未提供详细内容,但可推测涉及高铁跨海大桥区域的4G/5G网络覆盖规划,需考虑信号穿透、移动性管理、网络容量等因素。 总结: 提供的参考资料涵盖了4G/5G无线网络优化、网规案例分析、网络优化策略、资源利用、基站管理等多个方面。 通过具体案例分析,展示了无线网络优化中的常见问题及解决方案,如NSA终端掉4G、RSSI识别天馈遮挡、CA不生效等。 强调了5G网络优化与策略的重要性,包括CCE映射方式优化、5G语音解决方案、AAU扇区组网选择等。 提出了4G网络优化与资源利用的策略,如室分设备利旧、宏站RRU设备1托N射灯等。 基站与流程管理方面,提到了爱立信LTE基站邻区添加流程,但未给出具体细节。 新高铁跨海大桥覆盖方案试点展示了特殊场景下的网络规划需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值