操作系统实验一:进程和线程(1)

简介: 实验内容一、进程的创建编写一段源程序,使用系统调用fork()创建子进程,当此程序运行时,在系统中有父进程和子进程在并发执行。观察屏幕上的显示结果,并分析原因(源代码:forkpid.c)。

实验内容

一、进程的创建

编写一段源程序,使用系统调用fork()创建子进程,当此程序运行时,在系统中有父进程和子进程在并发执行。观察屏幕上的显示结果,并分析原因(源代码:forkpid.c)。

1、编辑源程序

ac4f46d74eb649d28db4d5bb26134e7b.png

2、编辑结果

81d69c4ab93945ce91e18a83913b58ae.png

3、编译和运行程序

5cec37fa7d7e4c6a87b2451e67233d31.png

4、解释运行结果

假设命令./forkpid创建的进程称为A,则A中的fork()调用会创建一个子进程,称为B

fork()函数只在父进程A中成功被调用一次,但是在A和B两个进程中都会有返回值。成功创建子进程后,在父进程A中,fork的返回值为创建的子进程的pid;在子进程中,fork的返回值为0。


因此,if(p1==0)后面的代码块会在子进程中被执行,而else后面的代码块会在父进程中被执行。由上述运行结果可知,进程的创建关系如下。

pid: 389733 --> 390075(A) --> 390076(B)

那么还有一个问题,上述运行结果中的My parent is 1,岂不是说B进程的父进程pid是1?这不是矛盾了吗?根据我所查阅的资料,这是因为父进程A比子进程B先结束,B中查找自己的父进程时,父进程A已经不在了,会使用pid为1的进程代替。在Linux中,pid号为1的进程是所有进程的祖先进程。


参考:


https://blog.csdn.net/lein_wang/article/details/81946108 - 为什么父进程id是1

https://www.cnblogs.com/alantu2018/p/8526970.html - linux的 0号进程 和 1 号进程

二、进程共享

父进程创建子进程后,父子进程各自分支中的程序各自私有,其余部分,包括创建前和分支结束后的程序段,均为父子进程共享。(源代码:forkshare_1.c)

1、运行

86323a31ff1b4c9ea1dd00096a3afa21.png

7c1ec98c531e4f86a01591d8f9d8f4d0.png

02997c7521414a79b85e6ddb92f3d51d.png

2、解释运行结果

根据运行结果我们很容易看出,进程调度顺序和上一节中一样,是先调度父进程再调度字进程。其中前三个字母xay是父进程的输出,后面三个字母xby则是子进程的输出。


可令人困惑不解的是,子进程不也应该从fork()返回的地方开始运行吗?那为什么'x'会被输出两次?我一时间有些蒙圈。


在查找资料后,我尝试了另一个示例,其中仅仅是将putchar('x')换成了printf("x\n"),而最主要的区别就是多了一个\n。下面是代码及其运行结果。

738b73a30a8b43b3ab41c3bd702cff9a.png

5a1c2573746b4a928b1006d576ac4ee1.png

子进程会从fork()返回的地方开始执行,没有错。问题出在stdout缓冲区的机制上,在输出xayxby的示例中,运行putchar('x')语句并没有直接将x写到屏幕上,而仅仅是将x放到了缓冲里。随后运行fork()创建子进程,会将父进程的stdout缓冲区也复制一份,而复制的缓冲区中就包含了刚刚放入的x。这便是两个x从何而来。


而如果打印的内容中包含了\n,就会马上将内容写到屏幕上并刷新缓冲区,这便是为什么第二个示例中x只有一个。

三、进程终止

如果子进程在其分支结束处使用了进程终止exit()系统调用而终止执行,则不会再执行分支结束后的程序段。(源代码:forkshare_2.c)

1、运行

7a471fa1a42f47f288ace7df04d4b123.png

ed741c7ccf03470baf5eaa1fb28a3044.png

2、解释运行结果

子进程因为在分支中输出b后就exit()结束进程了,因此子进程不会输出后面的y,其它与上一节情况相同,不作多解释。

四、进程同步

当父进程有许多任务要做时,往往会针对每一个任务创建一个子进程去完成,然后再等待每一个子进程的终止。其同步关系是父进程等待子进程 (源代码:wait.c)。

实现的方法是:1)子进程终止时执行exit()向父进程发终止信号,2)父进程使用wait()等待子进程的终止。

1、运行

 eded71b37462405ea88314c21b9bdd91.png

 ee5d29a1d6aa41fc9aea4492b1f15bcd.png

2、解释运行结果

前面三个程序都是父进程输出的 va在前而子进程输出的b在后,这次ba换子进程的输出在前了。


父进程中掉用wait(0)函数会立即阻塞自己,并等待子进程的退出。注意阻塞和空循环等待是有区别的,进程阻塞自己后会交出cpu的控制权。


不过,wait(0)和exit(0)中都有一个0,这个0是什么意思呢?其实这两个零并不是同一个东西,wait的函数头原型是int wait(int *status),参数是一个int类型的指针,wait(0)其实相当于wait(NULL),这个参数为NULL表示我们不关心子进程是如何退出的,只要退出就行。而如果你写成wait(1),那么编译时候就会报错了,因为类型不匹配。


而exit(0)的参数是退出码,你完全可以使用任性地使用exit(123)退出子进程,并在父进程中仍然以wait(0)接收子进程的终止信号。


参考:


https://zhuanlan.zhihu.com/p/549981187 - 入门篇:进程等待函数wait详解

https://blog.csdn.net/qustdjx/article/details/7704323 - wait()以及wait(&status)\ waitpid()

https://zhuanlan.zhihu.com/p/647776823 - Linux Shell 中的各种退出码

https://www.cnblogs.com/shikamaru/p/5359731.html - exit(0)、exit(1)、和return

五、Linux中子进程映像的重新装入

创建一个子进程,并给它加载程序,其功能是显示“I am a child”。设被加载的程序路径名为./child。分析:由于子进程需要加载的程序比较简单,不带参数,所以可以使用execl()实现加载。

1、运行

./child_parent.c:

5f73ca1dfeac4f068fab1ed640d80567.png

./child.c

87b12110a9b74606bad5a7436fa7e77b.png

413f4310b50742dda805ba0ff28b10b1.png

2、解释运行结果

我在./child_parent.c文件的execl()语句后加了一个printf()语句,这样也行能够更好地体现execl的运行机制。


上述源代码中共有两个printf语句,但最终只有./child.c中的printf语句产生了输出。这是因为,execl加载进程时并不会新建一个进程,而是使用传入路径中的程序覆盖当前进程,并从新进程的main函数开始执行。覆盖后,使用fork()创建的那个程序,当然也包括其中的printf("I am child A\n"),已经不存在了。


使用execl,子进程可以更好地干自己的活,而不只是作为父进程的拷贝。


参考:


https://blog.csdn.net/bao_bei/article/details/48287945 - Linux下execl函数学习_linux的execl函数

六、线程

Linux 系统下的多线程遵循 POSIX 线程接口,称为 pthread。编写 Linux 下的多线程程式,需要使用头文档 pthread.h,连接时需要使用库 libpthread。顺便说一下,Linux 下pthread 的实现是通过系统调用 clone()来实现的。clone()是 Linux 所特有的系统调用,他的使用方式类似 fork,关于 clone()的周详情况,有兴趣的读者能够去查看有关文档说明。下面我们展示一个最简单的多线程程序 pthread_create.c。

1、运行

6196056731d04dadb8ed7290374e6dd1.png

624e6c9519694124b2b0e8aee0ff64c5.png

2、解释运行结果

// 线程创建函数的函数头
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*strat_routine)(void *), void *arg)

返回值(int):创建成功时返回0,失败时返回错误码。


参数:


thread:前面先创建了空的线程标识符pthread_t id1,这里传入&id1告诉函数要将新建线程的标识符放到哪里。可以对比scanf("%d", &x)的用法。

attr:线程的属性,传入NULL表示默认。

start_routine:线程要执行的函数的指针,上述示例代码中实参前的(void*)大概是强制类型转换为指针的意思。函数头中那一串void *(*strat_routine)(void *)你可能看着有点晕,感觉自己的C语言功底不太够用了,抱歉我也是。

arg:线程要执行的函数所需要的参数,传入NULL意思是不需要参数。

我在main函数return前添加了一个printf语句。


pthread_join的作用是让主线程进入阻塞态,等待子线程运行结束后,主线程再接着运行。否则,从子线程创建时开始,主线程与两个子线程就开始并发执行,当主线程很快运行结束并return后,整个进程也会结束,即子线程跟着结束,都来不及在屏幕上打印信息。


可以看到输出中交替打印两个字符串各四次后,最后打印出来的是This is main thread.。


参考:


https://blog.csdn.net/sevens_0804/article/details/102823184 - Linux多线程操作pthread_t

https://www.jb51.net/article/176510.htm - 简单了解C语言中主线程退出对子线程的影响

操作系统实验一:进程和线程(2):https://developer.aliyun.com/article/1407234?spm=a2c6h.13148508.setting.18.79f64f0ecKMDuK

相关文章
|
19天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
1月前
|
资源调度 监控 算法
深入理解操作系统:进程管理与调度策略
本文旨在探讨操作系统中进程管理的核心概念及其实现机制,特别是进程调度策略对系统性能的影响。通过分析不同类型操作系统的进程调度算法,我们能够了解这些策略如何平衡响应时间、吞吐量和公平性等关键指标。文章首先介绍进程的基本概念和状态转换,随后深入讨论各种调度策略,如先来先服务(FCFS)、短作业优先(SJF)、轮转(RR)以及多级反馈队列(MLQ)。最后,文章将评估现代操作系统在面对多核处理器和虚拟化技术时,进程调度策略的创新趋势。
|
2天前
|
算法 调度 UED
深入理解操作系统中的进程调度策略
【4月更文挑战第28天】 在多任务操作系统中,进程调度策略是决定系统性能和响应能力的关键因素。本文将探讨操作系统中进程调度的基本原理、不同调度算法的特点及其适用场景,并通过分析比较它们的优缺点,提供一个全面的视角来理解操作系统如何管理运行中的进程。通过深入了解这些调度策略,读者可以更好地把握操作系统的行为模式,以及如何在特定应用中选择合适的调度策略以优化系统表现。
|
4天前
|
算法 Linux 调度
深入理解操作系统中的进程调度策略
【4月更文挑战第25天】 在多任务操作系统中,进程调度策略是核心组件之一,它负责决定哪个可运行的进程将获得CPU时间。本文将探讨不同的进程调度算法,包括它们的原理、优缺点以及适用场景。我们将重点分析先到先服务(FCFS)、短作业优先(SJF)、轮转调度(RR)和多级反馈队列(MLFQ)等经典算法,并讨论现代操作系统如Linux和Windows中的实际调度策略。文章的目的是为读者提供对操作系统进程调度机制深度了解,并展示其在系统性能和用户体验中的关键作用。
|
5天前
|
负载均衡 算法 调度
深入理解操作系统中的进程调度策略
【4月更文挑战第25天】 在现代操作系统的核心功能中,进程调度策略扮演着至关重要的角色。本文将详细解析进程调度的基本概念、调度算法的种类及其背后的原理,并探讨它们对系统性能的影响。通过比较不同的调度策略,我们可以更深入地理解操作系统如何管理资源,确保多任务环境下的效率和公平性。
|
6天前
|
Java 数据库连接 数据处理
Python从入门到精通:3.1.2多线程与多进程编程
Python从入门到精通:3.1.2多线程与多进程编程
|
13天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
29 0
|
22天前
|
算法 Linux 调度
深入理解操作系统的进程调度策略
【4月更文挑战第8天】本文深入剖析了操作系统中的关键组成部分——进程调度策略。首先,我们定义了进程调度并解释了其在资源分配和系统性能中的作用。接着,探讨了几种经典的调度算法,包括先来先服务(FCFS)、短作业优先(SJF)以及多级反馈队列(MLQ)。通过比较这些算法的优缺点,本文揭示了它们在现实世界操作系统中的应用与局限性。最后,文章指出了未来进程调度策略可能的发展方向,特别是针对多核处理器和云计算环境的适应性。
|
23天前
|
算法 调度 UED
深入理解操作系统中的进程调度策略
【4月更文挑战第7天】 在多任务操作系统中,进程调度策略是决定系统性能和响应速度的关键因素之一。本文将探讨现代操作系统中常用的进程调度算法,包括先来先服务、短作业优先、轮转调度以及多级反馈队列等。通过比较各自的优势与局限性,我们旨在为读者提供一个全面的视角,以理解如何根据不同场景选择合适的调度策略,从而优化系统资源分配和提升用户体验。
|
28天前
|
安全 Linux API
Android进程与线程
Android进程与线程
20 0