操作系统——进程管理

目录

准备工作

1. 编辑c语言源程序

复制代码
$vim   test.c
    ~
    ~
    ~
    ~ 

按键,进入编辑模式,程序输入完成后,按键,退出编辑模式。

按 : 键,进入命令行模式。所有需要在底部命令行执行的指令,都需要以:开头来触发。

  • :w 保存当前文件(write),但不退出vim。
  • :q (quit)不存盘并退出vim编辑,返回命令行方式。(若文件有未保存的修改,执行:q会报错)
  • :wq保存并退出,是最常用的命令。
  • :q!强制退出,不保存任何修改。

2. 编译c语言源程序

$gcc -o test -g test.c

没有错误提示,表示编译成功;否则返回vim中进行修改。

编译成功后,可通过 ./test 运行程序,或通过 gdb ./test 启动调试工具进行调试。

参数说明

  • gcc:GNU C 编译器,用于将 C 语言源代码编译为可执行程序
  • -o test:指定编译输出的可执行文件名为 test(如果不指定,默认输出文件名为 a.out
  • -g:在编译时添加调试信息,方便后续对程序进行调试(如设置断点、查看变量等)。
  • test.c:要编译的 C 语言源代码文件。

3. 调试运行

方式一:gdb ./test.c

$ gdb test

:

:<出现提示信息>

:

(gdb) 输入run ,程序即可运行,输出运行结果。

方式二:./test

通过 ./test 直接运行程序,得出结果

gcc 编译器的安装

ubuntu上没有装gcc编译器,需要自己安装。

安装命令如下:
sudo apt install gcc

由于上一次在安装了vim(shell编程(2)------基础知识结合小案例实现),本次安装应该没有任何问题。

安装完成后,使用gcc --version命令查看,证明安装成功。

一、关于进程的系统调用

  1. 进程的创建 fork()
    格式:pid=fork()
    功能:创建一个新进程,新进程与父进程具有相同的代码,父子进程都从fork()之后的那条语句开始执行。
  • a)对于父进程,pid 的值>0;
  • b)对于子进程,pid的值=0;
  • c)创建失败,pid的值<0。

pid 是 Process ID(进程标识符) 的缩写,是操作系统为每个运行中的进程分配的唯一整数标识符,用于唯一标识一个进程。

  1. 进程的终止 exit()

    格式:exit(status)

    功能:终止当前进程的执行,status是一个整数,其值可以返回父进程。

  2. 进程的同步 wait()

    格式:wait()

    功能:使父进程进入睡眠状态,直到子进程终止后被唤醒,用于实现父子进程的同步。

  3. 进程的延迟 sleep()

    格式:sleep(n)

    功能:当前进程延迟n秒执行。

  4. 进程执行另一程序 execl()

    功能:在当前进程中执行另一个程序,替换原有进程的代码、数据等,实现进程功能

二、 探究内容

1、进程的创建

执行下面的程序,分析执行的结果。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  // 包含fork函数的声明

int  main()
   {  int i;
      printf("just 1 process.\n");
      i=fork();
      if (i==0)
         printf("I am child.\n");
      else if (i>0)
    	 printf("I am parent.\n");
      else 
		 printf("fork() failed.\n");
      printf("program end.\n");
      return 0;
   }

分析

创建子进程:执行 i = fork()

fork() 会创建一个与父进程几乎完全相同的子进程,父子进程从 fork() 之后的代码开始分别执行。

父进程中,fork() 返回子进程的 PID(大于 0),因此 i > 0。

子进程中,fork() 返回 0,因此 i == 0。

若创建失败(极少发生),fork() 返回负数,执行 fork() failed.

结论

  • fork() 调用后,系统中会出现两个独立的进程(父进程和子进程),且从 fork() 之后的代码开始并行执行。
  • 父子进程通过 fork() 的返回值区分身份(父进程得到子进程 PID,子进程得到 0)。
  • 父子进程的执行顺序由操作系统调度决定,因此分支代码的输出顺序可能不固定,但初始的 just 1 process. 只会输出一次(仅父进程在 fork() 前执行)。

运行结果


2、进程的同步

c 复制代码
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include<sys/wait.h>		//wait()
int main()
   {  int i;
      printf("just 1 process.\n");
      i=fork();
      if (i>0)
         {	 
         		printf("I am parent.\n");
             wait();
         }
      else{
       if (i==0)
          { printf("I am child.\n");
            exit(1);
          }
        }
      printf("program end.\n");
   }

代码执行流程

  • 初始阶段:
    程序启动后,只有父进程运行,执行 printf("just 1 process.\n"),输出:just 1 process.
  • 创建子进程:执行 i = fork()
    • 父进程中,fork() 返回子进程的 PID(i > 0)。
    • 子进程中,fork() 返回 0(i == 0)。
  • 父进程分支(i > 0):
    • 执行 printf("I am parent.\n"),输出:I am parent.
    • 调用 wait():父进程进入阻塞状态,等待子进程终止后再继续执行。
  • 子进程分支(i == 0):
    • 执行 printf("I am child.\n"),输出:I am child.
    • 调用 exit(1):子进程立即终止,退出状态码为 1(该状态码会被父进程的 wait() 获取,但代码中未显式处理)
  • 后续执行:
    子进程终止后,父进程的 wait() 被唤醒,继续执行后续代码。
    父进程执行最后一句 printf("program end.\n"),输出:program end.
    子进程因已调用 exit(1),不会执行最后一句 printf(直接终止)。

关键特性

  • 同步机制:wait() 确保父进程等待子进程执行完毕后再继续,避免父进程先于子进程结束。
  • 子进程终止:exit(1) 使子进程主动退出,且不再执行后续代码(因此子进程不会输出 program end.)。
  • 输出顺序固定:由于 wait() 的作用,父进程必须等待子进程输出I am child.并终止后,才会继续输出 program end.,因此输出顺序是确定的。

结果

  1. 使用wait()函数时,需要在开始加入#include<sys/wait.h>头文件,否则会发生以下报错。

    2.wait 函数的声明是 pid_t wait(int *stat_loc);,它需要一个指向 int 的指针作为参数,用于接收子进程的退出状态信息。如果不需要获取子进程的退出状态,可以传递 NULL。

wait() 函数需要一个参数(用于存储子进程的退出状态)

这里根据需要,向wait()函数传入NULL就行了,我并不关注子进程的退出状态。

3.进程的延迟

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>	//fork(),sleep()
int main(){
	int i,j,k;
	i=fork();
	if(i>0)
		for(j=1;j<=5;j++){
			sleep(1);
			printf("I am parent.\n");
		}
	else
	if(i==0){
	for(k=1;k<=5;k++){
		sleep(1);
		printf("I am child.\n");
		}
	}
	return 0;
	}

分析

通过fork()系统调用创建一个子进程,父进程i > 0,执行一个循环,从 j=1到 j=5,每次循环休眠1秒后输出 I am parent.。子进程i == 0,执行另一个循环,从 k=1到 k=5,每次循环休眠1秒后输出 I am child.。父子进程并发执行各自的循环逻辑,输出顺序由操作系统调度决定,交替输出"父进程"和"子进程"的提示信息,最终各自完成 5 次输出后结束。

结果


4.进程执行另一程序

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(){
	int i;
	char name[20];
	printf("Please input a directory name:\n");
	scanf("%s",name);
	i=fork();	//单独赋值,避免优先级问题
	if(i==-1){
		perror("fork failed");
		return 1;
	}else if(i==0){
		execl("/bin/ls","ls","-l",name,NULL);
	else{
	
	}
}

结果


5.编写一段程序

要求:

编写一段程序,使用系统调用fork()创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符"a";子进程分别显示字符"b"和字符"c"。试观察记录屏幕上的显示结果,并分析原因。

代码:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    // 创建第一个子进程
    pid_t pid1 = fork();
    if (pid1 == -1) {
        perror("fork failed");
        return 1;
    }

    if (pid1 == 0) {
        // 第一个子进程:输出 'b'
        printf("b");
        return 0; 
    }

    // 父进程继续创建第二个子进程
    pid_t pid2 = fork();
    if (pid2 == -1) {
        perror("fork failed");
        return 1;
    }

    if (pid2 == 0) {
        // 第二个子进程:输出 'c'
        printf("c");
        return 0; 
    }

    // 父进程:等待两个子进程结束后输出 'a'
    wait(NULL); // 等待第一个子进程
    wait(NULL); // 等待第二个子进程
    printf("a");

    return 0;
}

结果


原因分析

  1. 进程创建机制

    • 父进程首先通过 fork() 创建第一个子进程(输出 'b'),再创建第二个子进程(输出 'c')。
    • fork() 后,子进程与父进程并发执行,操作系统的进程调度算法决定了它们的执行顺序(内核级调度,不受用户程序直接控制)。
  2. 输出顺序不确定性

    • 三个进程(父进程、子进程1、子进程2)的执行顺序由 CPU 调度决定。例如:
      • 可能子进程1先执行(输出 'b'),再子进程2(输出 'c'),最后父进程(输出 'a'),结果为 bca
      • 也可能子进程2先执行(输出 'c'),再子进程1(输出 'b'),最后父进程(输出 'a'),结果为 cba
  3. 父进程的等待逻辑

    • 代码中父进程通过 wait(NULL) 等待两个子进程结束后再输出 'a',因此 'a' 一定在最后输出(避免父进程先于子进程执行)。但两个子进程之间的输出顺序仍不确定。

总结

输出结果中 a 始终在最后,但 bc 的顺序不固定,这是因为 子进程与父进程的执行顺序由操作系统调度算法决定,属于正常的并发执行特性。若需固定顺序,需通过进程同步机制(如信号量、管道)额外控制。

相关推荐
我科绝伦(Huanhuan Zhou)3 小时前
分享一个可以一键制作在线yum源的脚本
linux·运维
Paper_Love4 小时前
Linux-查看硬件接口软件占用
linux·运维·服务器
wydaicls4 小时前
Linux 系统下 ZONE 区域的划分
linux·运维·服务器
带土14 小时前
17. Linux wc命令
linux
螺旋小蜗4 小时前
Linux Cgroup与Device Whitelist详解
linux·运维·服务器·cgroup
染指11104 小时前
36.渗透-端口
linux·运维·服务器
takashi_void4 小时前
如何在本地部署大语言模型(Windows,Mac,Linux)三系统教程
linux·人工智能·windows·macos·语言模型·nlp
大聪明-PLUS5 小时前
QEMU:如何组织与 I2C 设备的透明交互
linux·嵌入式·arm·smarc
sulikey5 小时前
【Linux权限机制深入理解】为何没有目录写权限仍能修改文件权限?
linux·运维·笔记·ubuntu·centos