进程编程基础

1. fork()函数

该函数用于从已存在的进程中创建一个新进程。新进程称为子进程,原进程称为父进程。使用该函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。
因为子进程几乎是父进程的完全复制,所以父子两个进程会运行同一个程序。因此需要用一种方式来区分它们,否则这两个进程只能做同样的事。
父子进程最重要的区别是:fork()的返回值不同。父进程中的返回值是子进程的进程号,而子进程中返回值是0,因此可通过返回值来判断该进程是父进程还是子进程。

函数原型:pid_t fork(void)
函数返回值:0 --- 子进程
		   大于0的正数(子进程PID) --- 父进程
		   -1 --- 出错

示例程序

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

int main(int argc, const char *argv[])
{
	pid_t ret;
	ret = fork();		//调用fork()函数

	if(ret == -1){
		perror("fork error");
		return -1;
	}
	else if(ret == 0){		//返回值为0代表子进程
		printf("In child process!!ret is %d,My PID is %d\n",ret,getpid());
	}
	else{					//返回值大于0代表父进程
		printf("In parent process!!ret is %d,My PID is %d\n",ret,getpid());
	}

	return 0;
}

编译并执行,结果如下:

linux@linux-virtual-machine:~/andy/proc$ gcc -o fork fork.c -Wall
linux@linux-virtual-machine:~/andy/proc$ ./fork 
In parent process!!ret is 2893,My PID is 2892
In child process!!ret is 0,My PID is 2893

2. exec函数族

exec函数族提供了一个在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代当前进程的数据段、代码段和堆栈段。在执行完之后,当前进程除了进程号外,其他内容都被替换了。
Linux中使用exec函数族主要有两种情况:

  • 当进程认为自已不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自已重生
  • 如果进程想执行另一个程序,那么它就可以调用fork()函数新建一个进程,然后调用exec函数族中的任一一个函数,看起来就像通过执行应用程序而产生一个新进程
函数原型:
int execl (const char *path,  const char *arg, ...)int execv (const char *path,  char *const argv[ ])int execle (const char *path,  const char *arg, ..., char *const envp[ ])int execve (const char *path,  char *const argv[ ], char *const envp[ ])int execlp (const char *file, const char *arg, ...)int execvp (const char *file,  char *const argv[ ]);
函数返回值:-1 表示出错
exec函数名 对应含义
前4位 统一为exec
第5位 l:参数传递方式为逐个列举(list)方式 (execl、execle、execlp)
v:参数传递方式为构造指针数组(vertor)方式 (execv、execve、execvp)
第6位 e:可传递新进程环境变量 (execle、execve)
p:可执行文件查找方式为文件名 (execlp、execvp)

示例程序

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

int main (void)
{
	if(fork() == 0){
		/*注意命令参数列表以NULL结尾*/
		if((execlp("ps","ps","-ef",NULL)) < 0)  //逐个列举方式传递参数,文件名方式查找可执行文件
		//if((execl("/bin/ps","ps","-ef",NULL)) < 0)  //逐个列举方式传递参数,文件目录方式查找可执行文件
		//char *arg[] = {"env",NULL};	//命令参数列表,必须以NULL结尾
		//char *envp[] = {"PATH=/tmp","USER=harry",NULL};	//命令参数列表,必须以NULL结尾
		//if((execle("/usr/bin/env","env", NULL,envp)) < 0) 
		//if((execve("/usr/bin/env",arg, envp)) < 0)
			printf("execlp error\n");
	}	
}

以上程序运行结果与在shell中直接输入命令" ps -ef "是一样的

3. exit()和_exit()函数

exit()和_exit()函数是用来终止进程的。当程序执行到此时,进程会无条件地停止剩下的所有操作,清除所有数据结构,并终止本进程的运行。两者的区别:

  • _exit()函数是直接使进程停止运行,清除其使用的内存空间及其在内核中的数据结构
  • exit()函数在终止当前进程前会检查该进程打开了哪些文件,并把文件缓冲区中的内容写回文件后,再终止当前进程
函数原型:
void exit(int status)void _exit(int status);
函数传入值:
status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般,0表示正常结束;其他数值表示出现了错误,进程非正常结束

exit()示例程序

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

int main()
{
	printf("Using exit...\n");
	printf("This is the content in buffer");
	exit(0);
}

编译并执行,结果如下:

linux@linux-virtual-machine:~/andy/proc$ ./exit 
Using exit...
This is the content in buffer

_exit()示例程序

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

int main()
{
	printf("Using exit...\n");
	printf("This is the content in buffer");
	_exit(0);
}

编译并执行,结果如下:

linux@linux-virtual-machine:~/andy/proc$ ./exit 
Using exit...

4. wait()和waitpid()函数

wait()函数用于使父进程(即调用wait函数的进程)阻塞,直到一个子进程结束或该进程接到一个指定的信号为止。如果该父进程没有子进程或其子进程已结束,则立即返回-1
waitpid()函数的作用和wait()一样,但它并不一定等待第一个终止的子进程。其有若干选项,可提供非阻塞版本的wait()功能

/*******wait()函数*******/
函数原型:pid_t wait(int *status);
函数传入值:status 指向的整型对象用来保存子进程结束时的状态
函数返回值:成功---已回收的子进程的进程号;失败---返回-1

/******waitpid()函数*****/
函数原型:pid_t waitpid(pid_t pid, int *status, int options);
函数传入值:pid -->大于0,回收进程ID等于pid的子进程
			   -->等于-1,回收任一子进程,此时等同于wait()
			   -->等于0,回收其组ID等于调用进程的组ID的任一子进程
			   -->小于-1,回收其组ID等于pid的绝对值的任一子进程
		   status 指向的整型对象用来保存子进程结束时的状态
		   options -->WNOHANG,若指定的子进程没有结束,则waitpid()不阻塞而立即返回0
		   		   -->WUNTRACED,为了实现某种操作,有pid指定的任一子进程已被暂停,
		   		      且其状态还未报告过,则返回其状态
		   		   -->0,同wait(),阻塞父进程,直到指定的子进程退出	   
函数返回值:大于0, 已经结束运行的子进程的进程号
		   等于0,使用WNOHANG且没有子进程退出
		   等于-1,出错

示例程序

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

int main()
{
	pid_t pid,ret;

	if((pid = fork()) < 0){
		printf("Error fork\n");
	}
	else if(pid == 0){
		sleep(5);
		exit(0);	//子进程正常退出
	}
	else{
		
		do{			//循环测试子进程是否退出
			ret = waitpid(pid,NULL,WNOHANG);	//调用waitpid,且父进程不阻塞
			if(ret == 0){		//若子进程还未退出,则父进程暂停1s
				printf("The child process has not exited\n");
				sleep(1);
			}
		}while( ret == 0);
	
		if( pid == ret)		//若发现子进程退出,打印出相应情况
			printf("child process exited\n");
		else
			printf("some error occured\n");
	}
}

编译并执行,结果如下:

linux@linux-virtual-machine:~/andy/proc$ ./waitpid 
The child process has not exited
The child process has not exited
The child process has not exited
The child process has not exited
The child process has not exited
child process exited

5. 进程相关命令

  • ps 显示当前的活动进程
  • top 显示所有正在运行的进程
  • /proc 该目录下存放系统中当前运行的每一个进程的信息
  • nice 改变进程的优先级
  • renice 与nice命令用法一样,不同之处是可以更新正在运行命令的调度优先级
  • jobs 查看已挂载的进程
  • bg 列出已停止或后台的进程
  • fg 将最近的进程带到前台
  • kill 关闭进程

关注我的公众号,共同交流学习嵌入式开发相关技术:
Linux进程编程基础_linux