进程章节总结性实验

简介: 进程章节总结性实验

进程实验课笔记

本节需要有linux基础,懂基本的linux命令操作即可。

Ubuntu镜像下载

https://note.youdao.com/s/VxvU3eVC

ubuntu安装

https://www.bilibili.com/video/BV1j44y1S7c2/?spm_id_from=333.999.0.0

实验环境ubuntu22版本,那个linux环境都可以,mit这套课程的实验环境是xv6,这种类unix远古的环境都可以,那么正常环境都是没问题的,当然windows环境应该也是可以的,这里主要是Linux环境。

gcc基础

这里用一个helloworld演示

注意linux所有文件的后缀都是无意义的,我们这里标识.c只是单纯标识

当然先写代码

一步到位:gcc hello.c

这条命令隐含执行了

(1)预处理

(2)编译

(3)汇编

(4)链接

这里未指定输出文件,默认输出为a.out

gcc编译C源码有四个步骤:

预处理 ----> 编译 ----> 汇编 ----> 链接

现在我们就用gcc的命令选项来逐个剖析gcc过程。

在该阶段,编译器将C源代码中的包含的头文件如stdio.h添加进来

参数:”-E”

gcc –E hello.c –o hello.i

第一步预处理的过程,我们可以看下系统给我们做了什么

只加了注释,这里看不到汇编代码,那就算了

继续下一步

编译

gcc –S hello.i –o hello.s

这里报错了

看起来不需要头文件

注释掉

重新预处理和编译,注意这里要重新来

可以了

我们看下文件

转化成汇编了,不出所料

不多深究了继续

链接 其实就是解析汇编代码了

gcc –c hello.s –o hello.o

链接其实就是做成exe文件

gcc hello.o -o hello

这步可以理解成加壳

注意要执行给权限,随便怎么给,我给777

lab1:获取进程号

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{ // getpid()函数返回当前进程的ID号
printf("Before fork Process id:%d\n", getpid());
printf("After fork Process id:%d\n", getpid());
pause();
return 0;
}

我们运行一下

我们打开另外一个terminal

实验二:子进程与父进程的pid

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//pid_t是数据类型,实际上是一个整型
//通过typedef重新定义了一个名字,用于存储进行ID
pid_t pid;
//parent pid
pid_t cid;
//child pid
// getpid()函数返回当前进程的ID号
printf("Before fork Process id:%d\n", getpid());
fork();
printf("After fork Process id:%d\n", getpid());
pause();
return 0;
}

为什么执行了三遍呢?

这里我们可以看出父进程执行了两次,而子进程执行了一次

我们再看下进程状况

至于为什么父进程执行了两边呢?

这就和我们系统创建子进程的过程相关了,创建子进程的第一步就是将父进程的内存数据全部clone一份,然后放入一份新的属于子进程的内存区域中。这样也就代表他们的内存中的text区域数据是一样的,这样他们就会执行相同的代码。当然这里是并发执行,那么父子进程都要执行一遍fork函数之后的代码,注意这里字进程是在fork函数执行时被唤醒的,所以子进程虽然有父进程的代码,但是子进程也只会并发执行fork函数后面的代码(也就是从fork函数开始执行)。

这里我们需要注意,这里并发执行虽然是父进程唤醒子进程,但是由于并发的机制让我们用户基本感受不到谁先执行,所以理论上来说我们可以让父进程执行的时间更长一些(比如调用write输出i/o设备产生的结果时间更长),这样就可以做到父进程还没有输出,但是子进程却已经输出结果了,让我们产生子进程优先执行的错觉。

实验三:fork函数返回值

先看下fork函数的返回值

#include <unistd.h>
int main(int argc, char const *argv[])
{
//pid_t是数据类型,实际上是一个整型
//通过typedef重新定义了一个名字,用于存储进行ID
pid_t pid;
//parent pid
//为了防止误判先给cid一个值
pid_t cid=8;
//child pid
// getpid()函数返回当前进程的ID号
printf("Before fork Process id:%d\n", getpid());
cid = fork();
printf("cid=%d\n", cid);
pause();
return 0;
}

那么这个0是什么呢?

如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的pid号,而对于子进程他返回值是0,这里猜测这个0大概率是内核模式。如果创建失败,cid返回值为-1

实验四:并发与父子内存数据复制

并发

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t cid;
//child pid
printf("Before fork Process id:%d\n", getpid());
cid = fork();
if (cid == 0){
// 该分支是子进程执行的代码
    printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
        for(int i = 0; i < 3; i++){
        printf("hello\n");
        }
}else{
// 该分支是父进程执行的代码
    printf("Parent Process id: %d\n",getpid());
    for(int i = 0; i < 3; i++){
    printf("world\n");
    }
wait(NULL);
}
return 0;
}

这里可以看出结果是父进程还是比子进程先执行,但是这个并看不出并发

我们将for循环增加次数直接增加到3000次

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t cid;
//child pid
printf("Before fork Process id:%d\n", getpid());
cid = fork();
if (cid == 0){
// 该分支是子进程执行的代码,for循环增加次数
    printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
        for(int i = 0; i < 3000; i++){
        printf("hello\n");
        }
}else{
// 该分支是父进程执行的代码,for循环增加次数
    printf("Parent Process id: %d\n",getpid());
    for(int i = 0; i < 3000; i++){
    printf("world\n");
    }
wait(NULL);
}
return 0;
}

这一段输出我们可以看出父进程与子进程是你执行一下我执行一下,前面那个是因为进程执行输出数据太少看不出并发执行。

父子内存数据复制

我们前面说子进程是将父进程的内存空间数据copy了一份,那么我们现在设置一个值看下子进程读取是否会有问题

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t cid;
//child pid
printf("Before fork Process id:%d\n", getpid());
int value = 100;
cid = fork();
if (cid == 0){
printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid())
for(int i = 0; i < 3; i++){
printf("hello(%d)\n",value--);
}
}else{
printf("Parent Process id: %d\n",getpid());
for(int i = 0; i < 3; i++){
printf("world(%d)\n",value++);
}
wait(NULL);
}
return 0;
}

这里我们可以看出子进程和父进程的value值确实是一样的,

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t cid;
//child pid
printf("Before fork Process id:%d\n", getpid());
cid = fork();
if (cid == 0){
printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
for(int i = 0; i < 3; i++){
printf("hello\n");
}
}else{
printf("Parent Process id: %d\n",getpid());
for(int i = 0; i < 1; i++){
printf("world\n");
}
//wait(NULL);
}
return 0;
}

我们ps -ef看下

我们可以看出这子进程的父进程是systemd这个进程

其实出现这个现象的原因是因为我们没有在父进程中加wait函数,那么父进程由于执行的太快,快到父进程唤醒子进程的过程还没开始,父进程就已经被执行完了,那么父进程已经进入终止状态,cpu使用权回到了systemd这个进程上,那么只能是systemd这个进程去调用子进程,所以现在子进程的父进程号就变成了systemd的进程号了。

解决上面这个问题,我们就必须要要让父进程等待子进程,直到子进程执行完再把cpu使用权返回给systemd。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t cid;
//child pid
printf("Before fork Process id:%d\n", getpid());
cid = fork();
if (cid == 0){
printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
for(int i = 0; i < 3; i++){
printf("hello\n");
}
}else{
printf("Parent Process id: %d\n",getpid());
for(int i = 0; i < 1; i++){
printf("world(%d)\n",getppid());
}
wait(NULL); //等待子进程结束,再返回,()里面参数一般是空指针
}
return 0;
}
相关文章
|
1月前
|
Linux Shell 调度
操作系统实验一:进程和线程(1)
实验内容 一、进程的创建 编写一段源程序,使用系统调用fork()创建子进程,当此程序运行时,在系统中有父进程和子进程在并发执行。观察屏幕上的显示结果,并分析原因(源代码:forkpid.c)。
135 0
操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)(三)
操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)(三)
157 0
操作系统实验五 基于内核栈切换的进程切换(哈工大李治军)(三)
|
1月前
|
Linux API
【操作系统】实验七 显示进程列表
【操作系统】实验七 显示进程列表
17 1
|
11月前
|
Linux 测试技术
Linux操作系统实验十一 进程管理(下)
Linux操作系统实验十一 进程管理(下)
76 0
|
1月前
|
调度 数据库
操作系统实验一:进程和线程(2)
七、共享资源的互斥访问 创建两个线程来实现对一个数的递加 pthread_example.c 1、运行
55 0
|
7月前
|
存储 算法 搜索推荐
操作系统实验四:进程调度
操作系统实验四:进程调度
117 0
|
7月前
|
数据可视化 Python
操作系统实验二:进程同步程序设计
操作系统实验二:进程同步程序设计
232 0
|
10月前
|
消息中间件 Linux
实验 进程通信【操作系统】
实验 进程通信【操作系统】
66 1
|
9月前
|
机器学习/深度学习 Ubuntu 算法
操作系统原理实验2:进程调度(在Ubuntu虚拟机gcc编译环境下
操作系统原理实验2:进程调度(在Ubuntu虚拟机gcc编译环境下
127 0
|
10月前
|
存储 Shell Linux
实验 Linux Shell实现模拟多进程并发执行【操作系统】
实验 Linux Shell实现模拟多进程并发执行【操作系统】
205 0

热门文章

最新文章