初学-Linux多进程-fork();vfork()、wait...;system等函数的使用

进程

进程是计算机中正在运行的程序的实例。每个进程都有自己的地址空间、内存、文件和设备、线程以及其他系统资源。操作系统通过调度和管理进程来实现多任务处理,使得多个进程可以同时运行并与用户交互。在操作系统中,进程是基本的资源分配单位,它可以独立运行,也可以与其他进程进行通信和协作。

进程是程序的一次运行活动。

常用命令

在linux系统中,有几个常用的命令对进程进行操作。查看进程命令ps和top。结束进程命令kill,以及管道和重定向命令。

  1. 静态显示系统进程信息(ps)

    ps指令是linux系统标准的进程查看工具,通过它可以查看系统中进程的详细信息

    常用参数有:

    a:显示所有进程(包括其他用户的进程)
    u:以用户为主的格式来显示进程情况
    x:显示没有控制终端的进程
    e:显示环境变量
    f:做全格式列出
    l:长格式显示
    r:只显示正在运行的进程
    p:按照进程ID列出进程
    

    例子:

    ps            # 显示当前用户进程
    ps -aux       # 显示全部跟用户有关进程的所有信息
    

    常与管道组合使用:

    ps -aux |grep a    # 显示有关a进程的所有信息
    
  2. 动态显示系统进程信息(top)

    top命令相当于windows系统中的任务管理器,top是一个动态显示的过程,他通过不断的刷新当前的状态以动态的显示进程。调用top之后,它将独占前台,直到用户终止该程序(ctrl + c)。

    语法:

    top [-d] | top [-bnp]
    

    常用参数:

    -b:以批处理模式操作。
    -c:显示完整的命令行。
    -d:屏幕刷新间隔时间。
    -I:忽略失效过程。
    -s:保密模式。
    -S:累积模式。
    -i:不显示闲置和僵死进程。
    -n:更新显示次数。
    

进程标识符

每个进程都有一个非负整数表示唯一的ID,叫做pid,类似我们的身份证。

编程调用getpid函数获取本身的进程标识符,调用getppid获取父进程的进程标识符。

Linux进程控制

进程创建

在Linux系统中,创建进程的方式有两种:操作系统创建,和父进程创建。操作系统创建的进程是平等关系的,而父进程创建的子进程不是平等关系,而且相互之间存在资源继承的关系。而且父进程创建的子进程又可以创建子进程,从而形成一个进程家族。

fork函数

系统创建进程的通用方法是使用函数fork();我们通过在Linux终端输入man 2 fork命令,我们看到fork的使用说明。在这里插入图片描述

该函数的返回类型为整形,若是父进程则放回值为一个正整数,是子进程的pid号。若为子进程,则返回值为0。我们可以写段代码进行试验。

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

int main()
{
    pid_t pid;
    printf("befor fork\n");
    printf("father process pid = %d \n",getpid());
    pid = fork();

    if(pid > 0)
    {
        printf("this is father;pid = %d\n",getpid());
    }
    else if(pid == 0)
    {
        printf("this is son; pid = %d\n",getpid());
    }
    return 0;
}

我们看到以下结果:
在这里插入图片描述

如果你使用man命令,发现他找不到这些,出现以下情况:

在这里插入图片描述

不要慌,按照以下步骤操作一下。

# 1、更新一下资源
sudo apt-get update   
# 2、安装标准c相关的帮助文档
sudo apt-get install libc-dev
sudo apt-get install glibc-doc

vfork函数

man 2 vfork命令看到的结果

#include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);

我们看到与fork函数一个样子,该函数也是放回一个整形,也是创建一个新的进程,但是他们两个函数是不一样的。与fork函数作比较,虽然功能和参数都类似,但是也有自己的独特之处。例如:

  1. fork函数创建子进程是对父进程的完全拷贝,所谓完全拷贝就是将父进程的代码完全复制一份运行,这样子子进程就能完全独立于父进程运行,这样子的代码具有良好的并发性;而vfork函数创建的子进程,是和父进程共享地址空间,子进程需要完全运行在父进程的地址空间上,子进程对地址空间的数据修改同样会影响到父进程。
  2. vfork函数创建的子进程会优先运行,当它执行完exit或者exec后,父进程才可以运行;而fork函数创建的子进程运行的优先级取决于系统的调度算法。

由于vfork函数不会复制父进程的地址空间,会节省系统大量的开销,运行速度也非常快。

我们同样可以用几个代码来看看效果。

首先我们用fork函数来写一段代码

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

int main()
{
    pid_t pid;
    int cnt = 0;
    printf("befor fork\n");
    printf("father process pid = %d \n",getpid());
    pid = fork();

    if(pid > 0)
    {
        while(1){
            printf("this is father;pid = %d\n",getpid());
            sleep(2);
            printf("father: %d \n",cnt);
        }
    }
    else if(pid == 0)
    {
        while(1){
            cnt ++;
            printf("this is son; pid = %d\n",getpid());
            sleep(2);
            if(cnt >= 5)
            {
                printf("the son exit\n");
                exit(0);
            }
        }
    }
    return 0;
}

运行结果如下:

hyx@hyx-virtual-machine:~/c_project/c_Process$ ./a.out
befor fork
father process pid = 6759
this is father;pid = 6759
this is son; pid = 6760
father: 0
this is father;pid = 6759
this is son; pid = 6760
father: 0
this is father;pid = 6759
this is son; pid = 6760
father: 0
this is father;pid = 6759
this is son; pid = 6760
^C

通过运行结果我们看到,父进程和子进程在同时运行,并且在父进程中,子进程更改了值,但是父进程这边没有。

那这边用vfork修改一下代码

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

int main()
{
    pid_t pid;
    int cnt = 0;
    printf("befor fork\n");
    printf("father process pid = %d \n",getpid());
    pid = vfork();

    if(pid > 0)
    {
        while(1){
            printf("this is father;pid = %d\n",getpid());
            sleep(2);
            printf("father: %d \n",cnt);
        }
    }
    else if(pid == 0)
    {
        while(1){
            cnt ++;
            printf("this is son; pid = %d\n",getpid());
            sleep(2);
            if(cnt >= 3)
            {
                printf("the son exit\n");
                exit(0);
            }
        }
    }
    return 0;
}

运行结果:

befor fork
father process pid = 6975
this is son; pid = 6976
this is son; pid = 6976
this is son; pid = 6976
the son exit
this is father;pid = 6975
father: 3
this is father;pid = 6975
father: 3
this is father;pid = 6975
^C

通过结果我们发现,vfork函数创建的子进程会优先执行,在调用exit函数之后,父进程才开始运行,并且在子进程中对cnt的值进行了修改,在父进程中这个值也被修改了。


进程等待

在Linux系统中,当多个进程同时进行的时候,进程间需要协作工作,可能用到进程等待的操作。进程间的等待包括父子进程间的等待和进程组内成员间的等待。

同时用了wait方法接受推出的状态,如果子进程退出状态,没有被收集,子进程将变成僵尸进程。

进程等待有两种方法:wait和waitpid。
在Linux系统终端中使用帮助命令main wait,得到函数以下信息:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);

pid_t waitpid(pid_t pid, int *wstatus, int options);

wait函数

wait函数返回类型为整形,wstatus为一个整形指针,用于存放子进程的结束状态。当wait被调用时,父进程处于挂起状态,只有当子进程结束返回。如果wait调用的父进程没有子进程,则返回失败。

调用成功:返回等待状态进程的pid; 调用失败:返回-1

我们同样用上面的代码的基础上,进行简单 的修改来看看情况。

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

int main()
{
	pid_t pid;
	int cnt = 0;
	int status;
	printf("befor fork\n");
	printf("father process pid = %d \n",getpid());
	pid = fork();

	if(pid > 0)
	{
		wait(&status);
		printf("status = %d\n",status);
		while(1){
			printf("this is father;pid = %d\n",getpid());
			sleep(2);
			printf("father: %d \n",cnt);
		}
	}
	else if(pid == 0)
	{
		while(1){
			cnt ++;
			printf("this is son; pid = %d\n",getpid());
			sleep(2);
			if(cnt >= 3)
			{
				printf("the son exit\n");
				exit(1);
			}
		}
	}
	return 0;
}

运行结果如下:

befor fork
father process pid = 7374 
this is son; pid = 7375
this is son; pid = 7375
this is son; pid = 7375
the son exit
status = 65280
this is father;pid = 7374
father: 0 
this is father;pid = 7374
^C

通过运行结果我们可以看到,用wait同样也可以使子线程优先运行。但是,接受到的status不是我们以为的值。那是因为,我们如果想要的是放回的值,那么我们还需要添加那些用于解释进程退出状态的宏。

我们将WEXITSTATU(status)代替status,我们就能得到正常的输出了。

在这里插入图片描述

waitpid函数

waitpid函数,返回值为整型,调用更加灵活,用于等待指定的进程。

status参数是个整形的指针用于存放子进程的结束状态。

pid参数是个整型参数,pid用于指定所等待的进程。

options参数指定所作的操作,取值0:表示进程挂起等待结束;取值WNOHANG表示不使进程挂起而即刻返回;取值WUNTRACED表示进程已经结束并返回。

调用成功返回等待状态的ID;调用失败返回-1。

我们魔改一下wait的代码,使用waitpid代替wait。代码如下:

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

int main()
{
	pid_t pid;
	int cnt = 0;
	int status;
	printf("befor fork\n");
	printf("father process pid = %d \n",getpid());
	pid = fork();

	if(pid > 0)
	{
		waitpid(pid,&status,0);
		printf("status = %d\n",WEXITSTATUS(status));
		while(1){
			printf("this is father;pid = %d\n",getpid());
			sleep(2);
			printf("father: %d \n",cnt);
		}
	}
	else if(pid == 0)
	{
		while(1){
			cnt ++;
			printf("this is son; pid = %d\n",getpid());
			sleep(2);
			if(cnt >= 3)
			{
				printf("the son exit\n");
				exit(37);
			}
		}
	}
	return 0;
}

运行结果:

befor fork
father process pid = 7530 
this is son; pid = 7531
this is son; pid = 7531
this is son; pid = 7531
the son exit
status = 37
this is father;pid = 7530
^C

通过运行结果,我们可以看到,用waitpid实现了wait函数同样的效果。


system函数

在linux环境下,我们使用man system看到system函数的信息如下:

#include <stdlib.h>

int system(const char *command);

我们可以在linux环境中使用system函数执行系统指令。command是一个字符串指针,指向表示命令的字符串。system函数可以执行系统命令,同时也可以调用fork、exec、waitpid。

接下来我们试一试:

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

int main()
{
	printf("show ls -a \n");
	system("ls -a");
	printf("done\n");
	exit(0);
	return 0;
}

结果如下:

show ls -a 
.  ..  01fork.c  02fork.c  03vfork.c  04wait.c	05waitpid.c  06system.c  a.out
done

结果与ls -a 效果相同。


总结

进程的相关操作是Linux编程的重要环节。熟悉这些进程控制的api的使用,对我们初学Linux平台下的c语言编程大有帮助。本文重点介绍了,进程的创建常用的俩函数,以及进程的等待。以及system函数。之后将重点介绍一下exec族。

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坏柠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值