【printf、主函数参数、fork/父子进程、僵尸进程】

17 篇文章 0 订阅

写时拷贝

一、printf输出与exit函数

(一、printf输出与exit函数)

在这里插入图片描述
上述程序执行完毕,睡眠了几秒,但 没有输出内容。

我们把_exit()改为exit(),再试一次
在这里插入图片描述
上述程序结果如下图:
在这里插入图片描述
这里的NIHAO成功打印。

1.exit()函数
所在头文件:stdlib.h(如果是”VC6.0“的话头文件为:windows.h)
功 能: 关闭所有文件,终止正在执行的进程。
exit(0)表示正常退出,
exit(x)(x不为0)都表示异常退出
,这个x是返回给操作系统(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。
2.exit()函数和_exit()函数区别

_exit()函数:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数与_exit()函数最大的区别就在于 exit()函数在调用 exit 系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,清理io缓冲。

二、主函数参数

argc 为参数个数;argv[]为参数

我们可以打印看到主函数的各个 参数。
请添加图片描述
如下图,就一个参数 这个参数是main的名字。
请添加图片描述
我们也可以给主函数传递参数,通过如下方式:
请添加图片描述

三、fork父子进程

1.fork()函数

fork系统调用用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。
它不需要参数并返回一个整数值。下面是fork()返回的不同值。
负值:创建子进程失败。
零:返回到新创建的子进程。
正值:返回父进程或调用者。该值包含新创建的子进程的进程ID [1] 。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
正常情况下子进程结束后把退出码给给父进程后,释放PCB 等内存空间。然而,PCB中存放一个叫做退出码的信息来记录进程的退出状态。如果子进程的退出码一直没有被接收,子进程的PCB不会被释放。
上图中的struct task_struct 结构体 指的是一个进程描述符

内核把进程存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct,成为进程描述符的结构,该结构定义在<linux/sched.h>文件中。进程描述符包含了一个具体进程的所有信息。

当父进程执行期间,子进程结束了,没有返回值给父进程,此时子进程是僵死的。可以注意到fork(defunct)
子进程先执行结束时的代码
僵尸进程
当父进程执行结束时,子进程没有结束,那么就会把INIT进程(pid一般为1,负责管理其他进程)作为他的“父亲”。
父进程先执行结束时的代码

我们可以看到1392这个地址和之间父进程6375的地址不同。
init 进程代替成为子进程的父亲
当我们用wait函数等待接收子进程的返回id后:

在这里插入图片描述
我们发现程序执行过程中,子进程并没是僵死状态,在wait返回id后子进程就被释放了。
父进程等待并接受了子进程的返回值

1.进程:正在运行的程序
进程:并发执行的程序在执行过程中分配和管理资源的基本单位。在这里插入图片描述
每个PCB都是一个结构体,里面存放进程信息。
在这里插入图片描述

2.并发与并行
并发

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

并行

当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

在操作系统中,若干个程序段同时在系统中运行,这些程序的执行在时间上是重叠的,一个程序段的执行尚未结束,另一个程序段的执行已经开始,无论从微观还是宏观,程序都是一起执行的。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。

并行无论宏观微观都是同时进行;并发宏观是同时的,微观其实是顺序的。也就是说并行是物理同时,并发是逻辑同时。

3.进程的状态
(1)运行(running)态:进程占有处理器正在运行。
(2)就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行。
(3)等待(wait)态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。

通常,一个进程在创建后将处于就绪状态。

四、内存管理和两个小问题

1.为什么把main.c变成可执行文件?
原因是不转成可执行文件(二进制文件),计算机将无法识别程序。
在这里插入图片描述
2.内存的分配

请添加图片描述
实际上的内存分配是碎片化的,也就是说是逻辑地址,我们指针的地址其实是逻辑地址。此方式的原因是按物理分地址,系统将无法识别这个地址是否被用过.
在内存分配时我们按页为单位,一页内存大小为4K,也就是说每4K一个物理页。

页表的作用是我 们可以根据逻辑页的页数(逻辑地址)找到对应物理页的物理地址。

问题2:当我们fork一个子进程时,两个进程的n在物理上是 同一块空间吗?
请添加图片描述
答案是否定的。我们在程序执行时会发现子进程和父进程n
的地址是相同的,其实这只是逻辑地址,物理地址重新给子程序的n分配了内存空间。
逻辑地址:其实是一个偏移量。
我们可以根据一个公式来用偏移量找到物理地址。

&n /4k =商 商是页位置
余数 是这个页的偏移量 用商 加上偏移量对比表查物理地址 (加上偏移量)

五、三道面试题


第一题

int main()
{
	fork() || fork();
	printf("A\n");
	//打印几个A?
	return 0;
}

答案是3个A;第一个fork的返回值是>0 的PI D号,并产生一个子进程2号。一号第一个A正常打印并结束。二号fork()返回值(||左侧那个)是0,所以要继续判断||后的程序,所以继续fork一个三号,此时二号打印了一个A;最后三号||右侧fork返回值为零,打印A,结果三个A。

第二题

int main()
{
	for (int i = 0; i < 2; i++)
	{
		fork();
		printf("A\n");
	}
	//打印几个A?
	return 0;
}

在这里插入图片描述

int main()
{
	for (int i = 0; i < 2; i++)
	{
		fork();
		printf("A");
	}
	//打印几个A?
	return 0;
}

答案是8个A。
在这里插入图片描述
上下中间的两个节点的fork各自继承了一个父进程缓冲区内的A。(因为缓冲区没有刷新)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值