天天看点

Linux之进程Linux之进程

Linux之进程

程序,科学的定义就是编译过的,可执行的二进制代码,这个很好理解。如果程序很大,可以叫做应用,这里提到的程序以及应用都是类似的概念。

进程是指正在运行的程序,一个程序中可以包含多个进程;一个进程可能包含一个或者多个线程。

一、进程ID

1、进程 id 基本概念

  1. 每一个进程都有一个唯一的标识符,进程 ID 简称 pid。
  2. 进程的 ID 在一个固定的时刻是唯一的,需要注意的是,假如你在 s 秒的时候有一个进程 ID是 1000,在另外一个时刻 s+n,另一个进程 ID 也有可能是 1000。
  3. 另外内核运行的第一个进程是 1,也就是内核的 init 程序,这个是唯一的。
  4. 进程 id 一般默认的最大值为 32768,不过也是可以修改的,当然一般情况下不需要这么做。如果当前进程是1000,那么下一个分配的进程就是 1001,它是严格线性分配的。直到 pid 到了最大值,才重新分配已经用过的进ID,当然这些进程都是已经死亡的进程。
  5. 除了 init 进程,其它进程都是由其它进程创立的。创立新进程的进程叫父进程,新进程叫子进程。

2、 使用 man 学习 getpid 和 getppid

如下图所示,使用命令“man 2 getpid”。有两个类似的函数 getpid 和 getppid。

Linux之进程Linux之进程

接着介绍一下 getpid 和 getppid 的用法。

pid_t getpid(void);
//参数:无。
//返回值:成功返回进程号。
pid_t getppid(void);
//参数:无。
//返回值:成功返回父进程。
           

3、函数例程

编写简单的 getpid.c 文件测试 getpid 和 getppid 函数。

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

void main()
{
	pid_t idp,id;
	
	idp = getppid();
	printf("ppid = %d\n",idp);
	
	id = getpid();
	printf("pid = %d\n",id);
}
           

4、运行结果

运行程序如下。

Linux之进程Linux之进程

如上图所示,pid1209是当前程序的进程号, ppid927是当前进程的父进程。

如何查看当前程序的进程

Linux 中有进程配套的命令 ps 个 kill,可以查看和终止进程。如下图所示,ps 命令查看进程号。

Linux之进程Linux之进程

如果是一个循环程序,执行到 while(1),运行的时候可以在执行命令后面添加&,后台运行,然后使用 ps 查询 id 号,使用 kill 命令结束进程。

二、执行新程序-exec 函数族一

在学习创建进程之前,先来学习一下 linux 中重要的 exec 函数族。在 linux 中,exec 函数族是把程序直接载入内存,而不是在一个程序中运行多个进程。

Linux之进程Linux之进程

如上图所示,最简单直白的解释就是 exec 函数族调用成功之后,会在内存中执行一个新的程序。在 linux 中要运行多任务需要使用 exec 函数族和 fork 进程。

1、 使用 man 学习 exec 函数

如下图所示,使用命令“man 3 exec”。有六个类似的函数 execl, execlp, execle, execv, execvp, execvpe 。

Linux之进程Linux之进程

exec 函数族比较多,但是却很容易记忆和区分的,下面给大家总结一下它们的区别。

如上图所示,首先函数族都是以 exec+xx 的方式命名的。

  1. “l”和“v”表示参数是以列表还是以数组的方式提供的。
  2. “p”表示这个函数的第一个参数是*path,就是以绝对路径来提供程序的路径,也可以以当前目录作为目标。
  3. “e”表示为程序提供新的环境变量。

2、函数例程

编写简单的 exec.c 文件测试 exec 函数。

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

//exec函数族
int main(void)
{
	if(execl("/mnt/udisk/helloexec","helloexec","execl",NULL) == -1){
		perror("execl error");
		exit(1);
	}
	//程序已经跳转走,如果正常execl不反回错误,下面的代码不会执行!
	printf("execl error!\n");
	return 0;
}
           

接着编写简单的 helloexec.c 文件,如下所示。main 函数的第二个参数传递进入之后,argv[1]将被打印出来,如果正常执行则会打印“Hello execl!”。

#include <stdio.h>

int main(int arc,char *argv[])
{
	printf("Hello %s!\n",argv[1]);
}
           

3、运行结果

运行程序如下。

Linux之进程Linux之进程

如上图所示,可以看到执行了 helloexec 的打印函数,调用成功。

三、fork 创建新进程

在 linux 中可以使用 fork 创建和当前进程一样的进程,新的进程叫子进程,原来的进程叫父进程。

1、 使用 man 学习 fork 进程

如下图所示,使用命令“man 2 fork”。

Linux之进程Linux之进程

接着介绍一下 fork 的用法。

pid_t fork(void);
//参数:无
//返回值:执行成功,子进程 pid 返回给父进程,0 返回给子进程;
//出现错误-1,返回给父进程。执行失败的唯一情况是内存不够或者 id 号用尽,不过这种情况几乎很少发生。
           

进程函数 fork 的返回值

系统函数 fork 调用成功,会创建一个新的进程,它几乎会调用差不多完全一样的 fork 进程。

子进程的 pid 和父进程不一样,是新分配的。

子进程的 ppid 会设置为父进程的 pid,也就是说子进程和父进程各自的“父进程”不一样。

子进程中的资源统计信息会清零。

挂起的信号会被清除,也不会被继承(后面章节进程通信中会介绍信号)。

所有文件锁也不会被子进程继承。

这里有一个很难理解的地方是,fork 函数的返回值问题。

fork 函数执行成功,子进程 pid 返回给父进程,0 返回给子进程;

出现错误-1,返回给父进程。执行失败的唯一情况是内存不够或者 id 号用尽,不过这种情况几乎很少发生。

2、函数例程

编写简单的 fork.c 文件测试 fork 函数。

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

main()
{
	pid_t pid;
	int i=100;
	
	pid = fork();
	//调用出错
	if(pid == -1){
		printf("fork failed\n");
		return 1;
	}
	//返回给父进程子进程号,返回值大于0
	else if(pid){
		i++;
		printf("\nThe father i = %d\n",i);
		printf("The father return value is %d\n",pid);
		printf("The father pid is %d\n",getpid());
		printf("The father ppid is %d\n",getppid());
		while(1);
	}
	//返回子进程0,返回值等于0返回给子进程
	else{
		i++;
		printf("\nThe child i = %d\n",i);
		printf("The child return value is %d\n",pid);
		printf("The child pid is %d\n",getpid());
		printf("The child ppid is %d\n",getppid());
		while(1);
	}
	return 0;
}
           

3、运行结果

运行程序如下。

Linux之进程Linux之进程

如上图所示,可以看到打印的父进程和子进程号,然后其中打印的 i 都是一样的。子进程可以使用父进程中定义的变量,和父进程中的变量却是不同的变量。

四、进程终止 exit

在 main 函数的结尾会使用 return 或者exit 结束程序。当使用 exit 的时候,就是使用的进程终止函数 exit。

1、使用 man 学习 exit 函数

如下图所示,使用命令“man 2 exit”。

Linux之进程Linux之进程

接着介绍一下 void exit(int status)的用法。

void exit(int status)
//参数:返回给父进程的参数。
//返回值:无。
//函数_exit 和 exit 没什么区别,不需要花太多时间去追究。
//创建进程还有一个 vfork 函数,感兴趣的可以通过网络找个例子实现以下,vfork 的使用会产生很多新问题,使用 vfork 创建的进程结束需要调用_exit。
           

五、exec 函数族+fork 进程+linux 命令+linux 时间函数例程

1、函数例程

编写简单的 execls.c 文件。

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

int main(void)
{
	char *arg[] = {"ls","-a",NULL};
	
	if(fork() == 0){
		//in child1
		printf("fork1 is OK;execl\n");
		
		if(execl("/bin/ls","ls","-a",NULL) == -1){
			perror("execl error");
			exit(1);
		}
	}

	usleep(20000);
	if(fork() == 0){
		//in child2
		printf("fork2 is OK;execv\n");
		
		if(execv("/bin/ls",arg) == -1){
			perror("execv error");
			exit(1);
		}
	}
	
	usleep(20000);
	if(fork() == 0){
		//in child3
		printf("fork3 is OK;execlp\n");
		
		if(execlp("ls","ls","-a",NULL) == -1){
			perror("execlp error");
			exit(1);
		}
	}
	
	usleep(20000);
	if(fork() == 0){
		//in child4
		printf("fork4 is OK;execvp\n");
		
		if(execvp("ls",arg) == -1){
			perror("execvp error");
			exit(1);
		}
	}
	
	usleep(20000);
	if(fork() == 0){
		//in child5
		printf("fork5 is OK;execle\n");
		
		if(execle("/bin/ls","ls","-a",NULL,NULL) == -1){
			perror("execle error");
			exit(1);
		}
	}
	
	usleep(20000);
	if(fork() == 0){
		//in child6
		printf("fork6 is OK;execve\n");
		
		if(execve("/bin/ls",arg,NULL) == -1){
			perror("execve error");
			exit(1);
		}
	}
	//加入小延时可以避免发生混乱的情况
	usleep(20000);
	return 0;
}
           

2、运行结果

先把 execls 文件拷贝到当前目录下,使用 ls 命令,查看当前目录,便于和后面执行程序之后对比。

Linux之进程Linux之进程

运行后部分结果如下图所示。

Linux之进程Linux之进程

如上图所示,可以和前面执行 ls 命令之后对比。

继续阅读