1、进程概念
①进程状态图
②进程内存映像
当父进程创建子进程时,子进程会复制父进程内存所有资源,从父进程处继承了整个进程的地址空间,包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等。
二、和进程有关的函数
1.进程的创建—fork()
#include<unistd>
#incude<sys/types>
pid_tfork();
函数返回值:执行成功则建立一个新的进程,在子进程中则返回0,在父进程中回返回新建子进程的进程号(PID),失败则返回-1。
备注:1.一般来说,在fork()之后是父进程还是子进程先执行是不确定的.这取决于内核所使用的调度算法.如果要求父子进程之间同步,则要使用某种形式的进程间同步进步.
2.子进程创建后就具有了自己的地址空间,因此在子进程中对变量的做的操作(n++),没有对父进程造成影响,这两个变量具有同样的名字,但在内存中位置是不同的。新进程的信息是复制而来,而非指相同的内存空间,因子进程对这些变量的修改和父进程并不同步。但是出了文件描述符。
2.vfork()
vfork函数和fork函数作用基本类似,但是也有一些重要区别。
因为调用vfork函数的目的是调用exec函数,所以没有必要完全复制父进程的数据区,所以在子进程调用exec之前,子进程是和父进程共享数据段的。所以这时子进程修改变量会影响父进程的变量。
另外一点不同是在执行次序上,fork对父子进程执行次序不限制,而在vfork中,子进程先运行,父进程挂起,直至子进程调用exec或exit之后。
3.exec函数
exec函数的作用是清除父进程的可执行代码映像,用新程序的代码覆盖调用exec进程的代码。exec执行成功后,进程将从新程序的main函数入口开始执行。
exec()函数实际上是一个函数族,共有6个不同的exec()函数可供使用,它们是:
#include<unistd.h>
intexecl (const char *pathname, const char *arg0, …);
intexecv (const char *pathname, char *const argv[]);
intexecle (const char *pathname, const char *arg0, …, char *constenvp[]);
intexecve (const char *pathname, char *const argv[], char *constenvp[]);
intexeclp (const char *filename, const char *argv0,…);
intexecvp (const char *filename, char *const argv[]);
exec函数族使用区别:
查找方式
前四个函数的查找方式都是完整的文件目录路径,而最后两个函数(以p结尾的函数)可以只给出文件名,系统就会自动从环境变量“$PATH”所指出的路径中进行查找.
参数传递方式
两种方式:逐个列举,将所有参数整体构造指针数组传递
以函数名的第五位字母来区分的,字母为"l"(list)的表示逐个列举的方式.其语法为char*arg;字母为"v"(vertor)的表示将所有参数整体构造指针数组传递,其语法为*constargv[].
环境变量
exec函数族可以默认系统的环境变量,也可以传入指定的环境变量.这里,以"e"(Enviromen)结尾的两个函数execle、execve就可以在envp[]中指定当前进程所使用的环境变量.
参数pathname指出目标文件的路径,参数filename指出目标文件名;以列表显示时,arg0须与pathname或filename相同,指示参数开始,最后以(char*)0结束,表示参数读完。以向量显示时,argv[]第一个元素也是和filename一样,最后一个元素为一个空字符串。
exec函数执行失败,常见原因:
找不到文件或路径,此时errno被设置为ENOENT;
数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
没有对应可执行文件的运行权限,此时errno被设置为EACCES.
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
intmain()
{
pid_tpid;
constchar *usr_envp[ ] = {"MYDEFINE=unknown","PATH=/tmp",(char *)0};
printf("Begin fork()\n");
pid= fork();
switch(pid)
{
case-1:
perror("forkfailed");
exit(1);
case0:
if(execle("/tmp/child","myarg1","my arg2",(char *)0, usr_envp)<0)
perror("execlefailed");
break;
default:
break;
}
if(waitpid (pid, NULL, 0) < 0)
perror("waitpidfailed");
printf("parent exiting\n");
exit(0);
}
4.进程的终止
一个进程正常终止有三种方式:
由main()函数return返回;
调用exit()函数;
调用_exit()或_Exit()函数.
exit()函数与_exit()函数最大的区别就在于exit()函数在终止当前进程之前要检查该进程打开过哪些文件,把文件缓冲区中的内容写回文件,就是上图中的"清理I/O缓冲"一项.
exit函数中可带状态码,用于在父进程中通过调用wait函数获得。
5.进程等待
wait()函数是用于使父进程(也就是调用wait()的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止.如果该父进程没有子进程或者他的子进程已经结束,则wait()就会立即返回。
waitpid()的作用和wait()一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的wait()功能,也能支持作业控制。
下面有几个宏可判别结束情况:
WIFEXITED(status)如果子进程正常结束则为非0值.
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用此宏.
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真.
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED来判断后才使用此宏.
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真.一般只有使用WUNTRACED时才会有此情况.
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED来判断后才使用此宏.
6.僵死进程
前面的学习中,我们已经了解了父进程和子进程的概念,并已经掌握了系统调用exit的用法,我们知道exit系统调用是安全终止进程的系统调用,它在真正终止进程之前要进行一些列的检查,其实,在一个进程调用了exit之后,该进程并完全消失掉(终止),而是留下一个称为僵尸进程(Zombie)的数据结构。僵尸进程是非常特殊的一种进程,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。