1.进程的定义
在讨论进程定义之前,我们可以先来了解一下计算机操作系统为什么要引入进程这个概念。
1.1 进程的引入
当计算机系统只有一个程序运行时,称之为单道程序,此时这个程序独占系统中的所有资源,在执行的过程中不受外界的影响;而多道程序在执行时,就是所谓的程序并发执行,即若干个程序同时在系统中执行,这时,这些程序就不可能独占所有的系统资源了,而需要多个程序共享系统的资源,从而导致各个程序在执行时出现相互制约的关系。为了刻画系统内部出现的这种动态情况,描述程序并发执行的活动规律,操作系统就引入了进程这个概念,进程的出现是为了使多个程序并发执行,用以改善资源利用率,并且提高系统的吞吐量。
1.2 进程的定义
进程是一个“执行中的程序”,即程序在处理机上执行时所发生的活动。
由上述简单的定义可以发现,进程与程序有着密不可分的关系,运行中的程序在内存中的映像就叫做进程。
1.3查看进程信息
在Windows系统下,可以通过任务管理器来查看系统中的进程信息。
在Linux系统下,可以通过命令 “ps -aux”来查看系统中的进程信息。如下图所示:
2.进程的基本操作
2.1 进程创建
在Linux下,提供了好几个关于创建进程的操作函数,如fork(), vfork()
(1)fork()函数
该函数的功能是创建一个进程,新进程为当前进程的子进程,当前进程就被称为父进程。该函数的调用形式为:pid_t fork(void);使用该函数要引用头文件<sys/types.h>和<unistd.h>头文件,函数返回值类型为pid_t,表示一个非负整数。若程序运行在父进程中,函数返回的PID为子进程的进程号;若运行在子进程中,返回的PID为0.若创建子进程失败,则会返回-1.
程序代码如下:
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid <0)
{
printf("fork error!\n");
exit(1);
}
else if(pid == 0)
{
printf("in the child process!\n");
}
else
{
printf("in the parent process!\n");
}
exit(0);
}
从程序的结果可以看出fork()函数的一个特点,即“调用一次,返回两次”,这又是为什么呢?
原来,在一个程序中,调用fork()函数后,就出现了分叉。在子进程中,fork()函数返回0;在父进程中,fork()函数返回子进程的PID。但是,调用fork()之后,谁先执行完全由调度器决定。
(2)vfork()函数
与fork()函数相同,这两个函数都是系统调用函数。而两者的区别是在创建子进程时fork()函数会复制父进程的所有资源,包括进程环境、内存资源等。而vfork()函数在创建子进程时并不会复制父进程的所有资源,父子进程共享地址空间。这样,在子进程中对虚拟地址空间中的变量修改,实际上是在修改父进程虚拟地址空间中的值。
以下是个实例,演示两个函数的区别。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int gvar = 2;
int main()
{
pid_t pid;
int var = 5 ;
printf("process id: %ld\n", (long)getpid());
printf("gvar = %d var = %d\n",gvar, var);
pid = vfork();
if(pid<0)
{
perror("error!\n");
return 1;
}
else if(pid ==0)
{
gvar--;
var++;
printf("the child process id: %ld\ngvar = %d var=%d\n",(long)getpid(),gvar,var);
_exit(0);
}
else
{
printf("the parent process id: %ld\ngvar = %d var=%d\n",(long)getpid(),gvar,var);
return 0;
}
}
结果如下:
由运行结果可以看出,父进程中输入的变量值也是在子进程中变化后的值。由此可知,调用vfrok()函数改变子进程中的值,其实就是改变父进程中的值。
2.进程等待
进程等待就是为了同步父进程与子进程,通常需要通过wait()函数使父进程等待子进程结束。如果父进程没有调用等待函数,子进程就会进入“僵尸(Zombie)”状态。
Linux提供的等待函数原型如下:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
pid_t waitpid(pid_t pid,int* status, int options);
wait()函数系统调用的工作过程是:首先判断子进程是否存在,即是否成功创建了一个子进程。如果创建失败,子进程不存在,则会直接退出进程,并且提示相关错误信息;如果创建成功,那么wait()函数会将父进程挂起,直到子进程结束,并且返回结束时的状态和最后结束的子进程的PID。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
/*定义一个功能函数,通过返回的状态,判断进程是正常退出还是信号导致的退出*/
void exit_s(int status)
{
if(WIFEXITED(status))
{
printf("正常退出,status=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("信号退出,status=%d\n",WTERMSIG(status));
}
}
int main()
{
pid_t pid, pid1;
int status;
//创建一个子进程
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid ==0) //子进程
{
printf("the child process!\n");
exit(2); //调用exit()退出函数正常退出
}
if(wait(&status)!=pid) //在父进程中调用wait()函数等待子进程结束
{
printf("This is a parent process!\nwait error!\n");
exit(0);
}
exit_s(status); //wait()函数调用成功,调用自定义的功能函数来判断退出类型
/*又一次创建子进程,在子进程中,使用kill()函数发送信号,导致退出*/
if((pid=fork()) <0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
printf("the child process!\n");
pid1 = getpid();
/*使用kill()函数发送信号*/
kill(pid1,19);
}
if(wait(&status)!=pid1)
{
printf("This is a parent process!\nwait error!\n");
exit(0);
}
exit_s(status);
exit(0);
}
提示:在Linux系统的终端输入“kill -l”命令,可以列出信号的具体情况、信号类型和其所对应的数字。
关于进程等待函数,还有一个常用的等待函数waitpid(),该函数实现的功能与wait()函数相同,但它们区别在于:wait()函数用于等待所有子进程的结束,而waitpid()函数仅用于等待某个特定进程的结束,这个特定的进程是指其pid与函数中的参数pid相关时。所谓的相关,有如下几种可能:
(1) pid=-1,等待任一个子进程,与wait()等效
(2)pid>0, 等待其进程ID与pid相等的子进程。
3.进程结束
当想要终止或者结束一个进程时,会使用系统调用exit()函数正常退出进程。该系统调用包括exit()和_exit()两个函数。
1.exit()函数
原型为:
#include <stdlib.h>
void exit(int status);
注意:该函数调用成功与失败都没有返回值,并且没有出错信息的提示。
exit()函数的作用是终止进程,并将运算status&0377表达式后的值返回给父进程,在父进程中通过wait()函数来获得。
2._exit()函数
原型为:
#include <stdlib.h>
void exit(int status);
同上,无论成功与否,都没有返回值。
在之前的进程创建时,vfork()函数创建的子进程在退出时只能使用_exit()函数退出进程,而不能使用exit()函数来退出。这是因为在调用exit()函数时,会对输入/输出流进行刷新,释放所占用的资源以及清空缓冲区等;而 _exit()函数则不具备刷新缓冲区等操作的功能。
4.进程组
所谓的进程组,就是一个或者多个进程的集合。作为一个进程组,里面的每一个进程都有统一的进程标识。
在Liunx系统中,可以通过调用函数 getpgrp()来获取进程组ID,该函数的原型为:
#include <sys/types.h>
#include <unistd.h>
pid_t getpgrep(void);
调用该函数可以返回调用该函数的进程所在的进程组ID。在进程组中有一个特殊的进程,该进程的ID与进程组的ID相同。
每一个进程都有其生命周期,从创建进程到进程终止,这是一个进程的生命期,而进程组的生命期是从该进程组的创建到最后一个进程终止。在Linux系统中,可以使用setpgid()函数创建一个新的进程组或者将一个进程加入到一个进程组中。该函数原型为:
#include <sys/types.h>
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
//调用成功返回0.失败返回-1;
以下为该函数的应用:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int a ;
pid_t pgid, pid;
pid = (long)getpid();
pgid = (long) getpgrp();
a = setpgid(pid, pgid);
printf("a = %d, pid = %ld, pgid = %ld\n", a, pid, pgid);
return 0;
}
结果:
注意:在setpgid()函数中,如果参数pid和pgid相等,则该函数功能是创建一个新的进程组;如果两个值不同,并且pgid是一个已经存在的进程组,那么该函数的功能是将pid进程加入到pgid这个进程组中。