Linux系统编程(二)进程


  

一、什么进程?

   进程是一个计算机程序在执行中的实例。通俗地讲,进程就是一个正在运行的程序,它不仅包括程序的代码,还包括程序所使用的数据、程序的运行状态和操作系统为其分配的资源。
   通俗来讲,想象你在电脑上打开了一个应用程序,比如一个文本编辑器。当你点击图标启动应用程序时,操作系统会创建一个进程来运行这个程序。这个进程包含了程序的所有代码和它需要的数据。你的电脑可以同时运行多个程序,比如你可以一边听音乐一边浏览网页。这是因为操作系统可以管理多个进程,并在它们之间切换,使它们看起来像是同时在运行。
   进程有一个生命周期,从创建、执行到终止。操作系统负责创建进程,调度它们运行,并在它们完成工作后清理资源。

   我们可以在linux系统上使用top命令来查看系统的负载情况、各个进程的资源使用情况、内存使用情况、CPU 使用情况等。
按下数字1可以查看多个cup详细使用情况。 在这里插入图片描述

二、进程地址空间划分

  1. 文本段(Text Segment):也称为代码段,用于存储程序的机器代码指令,通常是只读的。

  2. 数据段(Data Segment):包括初始化的全局变量和静态变量,以及显式初始化的局部静态变量。

  3. BSS 段:包括未初始化的全局变量和静态变量,在程序启动时会被系统初始化为零值。

  4. 堆(Heap):用于动态分配内存,即运行时动态分配的内存空间。在堆上的内存需要由程序员显式申请和释放,通常使用 malloc()、calloc()、realloc() 等函数进行分配,使用 free() 函数进行释放。

  5. 栈(Stack):用于存储函数的局部变量、函数参数、函数调用的返回地址等信息。栈是一种先进后出(FILO)的数据结构,函数调用时会分配栈帧,函数返回时会释放栈帧。

在这里插入图片描述

三、进程的特点

  1. 独立性:每个进程都是独立的,它们运行在自己的内存空间中,不会直接干扰其他进程。这种独立性确保了一个进程的错误不会直接影响到其他进程。
  2. 资源拥有:进程拥有自己的资源,比如内存、CPU时间、文件句柄等。操作系统通过进程管理这些资源,并在进程间分配它们。
  3. 状态切换:操作系统可以在不同的进程之间切换,使用户感觉多个进程在同时运行。这种能力称为“多任务处理”。

四、进程创建

创建进程的方式有两种:fork以及vfork

1. 相同点

   创建进程时会返回两个返回值,这与平时的函数不同(平时的函数只有一个返回值)。返回值为0的为子进程。返回值大于0的为当前进程,也就是父进程。
   无论是哪种创建方式,只要在forkvfork创建进程后定义的变量或者其他内容,子进程和父进程都是一人一份,子进程只执行返回值等于0的程序,父进程执行返回值大于0的程序。两个进程互不干扰,各自执行各自的程序。

例如:我们使用fork创建进程后,定义了变量n=10,这里的变量n每个进程都有一份,互不影响,所以当父进程的n++时,并不会影响子进程中的n的内容,所以子进程中n还是为10。

#include <stdio.h>
#include <unistd.h>

int main(int avgc,char **argv)
{

    pid_t pid;
	pid=fork();  //创建进程
  /*
    从创建进程开始,下面的内容子进程和父进程每人一份。子进程只执行pid==0的程序,父进程执行pid>0的程序。两个进程互不干扰,各自执行各自的程序。
  */
	int n=10;
	if(pid==0)
	{
      while(1){
         printf("我是子进程:%d\n",n);
		 sleep(1);
	   }
	}
	
	else if(pid>0)
	{
       while(1)
       	{
       	  n++;
          printf("我是父进程:%d\n",n);
		  sleep(1);
	    }
	}
	
    return 0;

2. 不同点

(1)执行顺序不同
      fork: 创建的父子进程同时运行,执行顺序无先后之分,执行顺序不一定。
      vfork:创建的父子进程,父进程必须要等子进程退出后才能执行。先执行子进程,后执行父进程。

举例如下:
●使用fork

#include <stdio.h>
#include <unistd.h>
	
int main(int avgc,char **argv)
{
    pid_t pid;
	pid=fork();     //方式一:fork创建进程
	if(pid==0)
	{
      while(1){
        printf("我是子进程\n");
		sleep(1);
	   }
	}
	
	else if(pid>0)
	{
       while(1)
       	{
          printf("我是父进程\n");
		  sleep(1);
	    }
	}
	
    return 0;
}

运行结果:
在这里插入图片描述

●使用vfork

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
	
int main(int avgc,char **argv)
{
    pid_t pid;
	pid=vfork();     //方式一:fork创建进程
	int n=0;
	if(pid==0)
	{
      while(1){
        n++;
        if(n>3) exit(0);  //设置退出条件
        printf("我是子进程\n");
		sleep(1);
	   }
	}
	
	else if(pid>0)
	{
       while(1)
       	{
          printf("我是父进程\n");
		  sleep(1);
	    }
	}

    return 0;
}

运行结果:
在这里插入图片描述

(2)复制与共享地址
   fork:创建进程时,操作系统会复制一份父进程的所有内容和整个地址空间,包括全局变量、代码段、数据段、堆和栈。子进程与父进程几乎完全相同,各自操作各自的变量和地址,互不影响。因为复制整个地址空间,fork 的性能开销相对较大,但现代操作系统通常通过“写时复制”技术来优化性能。
   vfork :与 fork 不同,vfork 不会复制父进程的地址空间和内容。而是子进程与父进程共享在创建进程前定义的所有变量以及地址空间,直到子进程调用 exec 或 _exit。即当父进程中的n++时,子进程中查看n时,也会进行改变。vfork 的设计目的是提高性能,特别是在立即调用 exec 执行新程序的情况下。

举例如下:
●使用fork

#include <stdio.h>
#include <unistd.h>
	
int main(int avgc,char **argv)
{
    pid_t pid;
    int n=0;  //在创建进程前定义
	pid=fork();  //创建进程

	if(pid==0)
	{
      while(1){
        n++;
        printf("我是子进程,n=%d\n",n);
		sleep(1);
	   }
	}
	
	else if(pid>0)
	{
       while(1)
       	{
       	  n--;
          printf("我是父进程,d=%d\n",n);
		  sleep(1);
	    }
	}
    return 0;
}

运行结果:
在这里插入图片描述

●使用vfork

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int avgc,char **argv)
{

    pid_t pid;
    int n=0;
	pid=vfork();  //创建进程
	
	if(pid==0)
	{
      while(1)
	  	{
	  	    n++;
	  	    printf("我是子进程:%d\n",n);
	  	    sleep(1);
		  	if(n == 5)  exit(0);  //循环5次,退出子进程
	   }
	}
	else if(pid>0)
	{
       while(1)
       	{
       	  n++;
		  if(n==10) exit(0);
          printf("我是父进程:%d\n",n);
		  sleep(1);
	    }
	}

    return 0;
}

运行结果:
在这里插入图片描述

五、进程退出

  进程的退出除了使用Ctrl+c等强制退出手段外,还有两种自然退出方式:exit (0)_exit (0)

区别如下:
区别:

1.什么是缓存IO呢?

  答:缓存IO是一种通过使用缓存技术来提高数据访问性能的输入输出操作方式。它主要涉及将频繁访问的数据存储在更快的存储介质(例如内存)中,以减少对较慢存储设备(例如硬盘、SSD)的访问次数和延迟。缓存I/O的主要目标是优化系统性能,提升数据读写效率。

举例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	while(1)
	{
	   printf("hello");
	   sleep(1);
	}
  return 0;
}

我们运行该代码,发现并没有输出内容,这是为什么呢?
  答:因为程序将” hello “存放到了IO缓冲区里,当IO缓冲区存满后或者遇到结束标志,如‘\n’。就会将缓冲区的内容输出到屏幕上。知道原理后,我们再写一个代码,清除的认识exit和_exit的区别。
在这里插入图片描述

2. exit()

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{

	   printf("hello");
	   exit(0);
}

  我们使用exit结束进程时,发现屏幕上既然有内容输出。这是因为exit函数将缓存区的内容输出了出来。那么我们将exit函数换为_exit,会有什么效果呢?
在这里插入图片描述

3. _exit()

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{

	   printf("hello");
	   _exit(0);
}

  我们运行程序,发现什么内容都没有输出。这是因为调用_exit()后,将缓冲区的内容全部丢弃导致的。这样我们就更清除的了解了这两个函数的区别。通常情况下我们都会使用exit()来结束进程。
在这里插入图片描述

六、进程状态

  在操作系统中,进程的管理和状态转换是一个复杂且重要的机制。孤儿进程和僵尸进程是其中两个特定的状态,它们各自有不同的形成原因和处理方法。

1. 孤儿进程

定义:孤儿进程是指其父进程已经终止,但它自己仍在运行的进程。
  在Unix和Linux系统中,孤儿进程会被操作系统自动重新父化给 init 进程(PID为1),即 init 进程成为这些孤儿进程的新父进程。init 进程负责监视这些孤儿进程并在它们终止时正确地回收它们的资源,避免资源泄露。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
  pid_t pid;
  pid=fork();
  if(pid==0)
  { 
     while(1){
         printf("子进程\n");
         sleep(1);
     }
  }
  else if(pid>0)
  {
     printf("父进程\n");
     exit(0);        //父进程退出,子进程继续运行。
   }
}

在这里插入图片描述

  这时我们使用另一个终端使用ps -ef查看进程。发现该进程的父进程已经变为1。我们可以使用 kill -9 7686来结束该孤儿进程。
在这里插入图片描述

2. 僵尸进程

定义:子进程已经终止,但父进程尚未读取其退出状态,则子进程形成僵尸进程。
  当一个子进程结束时,操作系统会发送一个信号(SIGCHLD)给父进程,通知它子进程已经结束。父进程需要调用wait()waitpid()系统调用来读取子进程的退出状态。如果父进程没有执行这一操作,子进程的退出状态将一直保留在系统中,从而形成僵尸进程。
注意:一定要避免僵尸进程,因为僵尸进程会一直占用系统资源。而孤儿进程会有init进程来负责清理其资源。

僵尸进程如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
  pid_t pid;
  pid=fork();
  if(pid==0)
  { 
      printf("子进程\n");
      exit(0);
  }
  else if(pid>0)
  {
     while(1){
         sleep(1);
         printf("父进程\n");
      }
   }
}

在这里插入图片描述
此时我们再打开一个终端,查看进程信息。发现下面标注的进程就为僵尸进程。
在这里插入图片描述

●使用等待函数,等待子进程退出,并清理其资源。

等待函数有:wait()waitpid()
  wait 函数:会阻塞父进程,直到等待到任意一个子进程退出,并返回该子进程的 pid。
  waitpid 函数:等待指定 pid 的子进程退出,并且它的行为不会被其他子进程所阻塞。如果为-1 表示等待所有子进程,同样返回该子进程的 pid。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);  //waitpid(pid, &n, 0)
//status 参数是传出参数,它会将子进程的退出码(进程的返回值)保存到 status 指向的变量里。如果不关心子进程的终止状态,可以传递NULL。
// int options :通常传0。

例程:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  pid_t pid;
  pid=fork();
  if(pid==0)
  { 
      printf("子进程\n");
      exit(123);   //设置退出号
  }
  else if(pid>0)
  {
     int n;
     wait(&n); //等待子进程退出,
     while(1){
         sleep(1);
         printf("父进程,%d号的子进程已退出\n", WEXITSTATUS(n));
      }
   }
}

在这里插入图片描述

这时我们查看后台进程,发现没有产生僵尸进程。
在这里插入图片描述

七、system函数

  system 函数是一个标准的 C 库函数,用于在程序中执行 shell 命令。它提供了一种简单的方法来调用操作系统的命令解释器,并执行指定的命令字符串。运行代码后,会直接输出命令执行的内容。

#include "stdio.h"
#include "stdlib.h"

int main()
{
    system("ls -al");
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值