进程与控制
该小节比较粗略的阐述了unix操作系统中用于进程控制的三个函数,分别为 : fork , exec, waitpid, 其中 exec 有五种变体, 这些变体实现的功能全部相同,只是参数不相同而已,他们统称为__exec__函数。
API
#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);
pid是一个宏定义,根据平台不同而不同,大多数情况都是一个int,该函数的难点在与对进程的理解,因为该函数会创建一个进程,创建进程以后,当前的调用进程会“分叉”, 所以该函数会在父进程与子进程中分别返回不同的值, fork 成功之后会获得父进程空间的数据,堆栈资源的副本,就是深拷贝的数据副本,2个进程同时独自享有这些资源,在fork成功之后,我们无法得知子进程与父进程的运行的先后顺序,这依赖于具体实现,虽然进程创建成功之后有了2个“同时”运行的进程, 但是我们的代码是只有一份(其实是2份,但是运行的效果是相同的,无法分别),所以就出现了这种情况:
if(pid<0)
{
fprintf(stderr,"错误!");
}
elseif(pid==0)
{
printf(" child running");
exit(0);
}
else
{
printf("father running and child is : %d\n",pid);
}
我们在程序中可以判断到底是哪个进程在运行,同时根据不同的进程采取不同的运行路径,为什么一段代码可以被2个进程执行呢,因为2个进程拥有相同的资源。即上面的代码其实是2个进程同时运行的(同时运行相同的代码,不是真的2个进程在运行同一段代码),只是看起来像是只有一个进程而已。
exec 系列函数
<unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve是真正的api,其他的函数都是对此函数的封装,为了实现方便快捷的调用而已。该函数首先查找要执行的文件,第一个参数可以是文件路径和文件名,也可以只是文件名。函数不同,参数不同,不过大致都是可执行文件的位置,其中第二个参数必须是可执行文件的 文件名, 可以存在于arg[0], 或者是arg0 , 总之都要是文件名, 往后的参数都是该命令的参数,该函数参数列表的结尾部分 必须是NULL, 不管是字符指针数组还是单独的若干个字符数组,其最后的一项必须为NULL。
该函数会替换当前正在执行进程本身,意思就是说当该函数执行成功,当前进程就会变成该函数启动的进程,并且调用进程会停止运行,当exec的进程执行完毕之后,调用进程也不会继续执行,因为它不是被挂起了,而是被替换掉了。
#include
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int * status,int options);
该函数用来监控进程状态,第一个参数是目标进程pid,第二个参数是进程状态返回地址,可选参数提供了多种可选方案。
参数pid也可以有别的可选值:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
pid=-1 等待任何子进程,相当于 wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为 pid 的子进程。
可选参数含义如下,摘选自百度百科
WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程。
子进程的结束状态返回后存于 status,下面有几个宏可判别结束情况:
WIFEXITED(status)如果若为正常结束子进程返回的状态,则为真;对于这种情况可执行WEXITSTATUS(status),取子进程传给exit或_eixt的低8位。
WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)若为异常结束子进程返回的状态,则为真;对于这种情况可执行WTERMSIG(status),取使子进程结束的信号编号。
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真;对于这种情况可执行WSTOPSIG(status),取使子进程暂停的信号编号。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。
如果执行成功返回子进程pid, 失败则返回-1, 错误识别码在 errno 中。
该节的例子程序
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 255
char ** string_split(char*, char*);
int parse_cmd(char*);
char **
string_split(char * str, char * split)
{
char ** ret_split = (char **)malloc(sizeof(char * ) * MAXLINE);
char * temp;
temp = strtok(str, split);
int i = 0;
while(temp)
{
*(ret_split + i) = temp;
i++;
temp = strtok(NULL, split);
}
*(ret_split +i) = NULL;
return ret_split;
}
int
parse_cmd(char * cmd)
{
char ** cmds = string_split(cmd, " ");
int signal = execvp(*(cmds + 0), cmds);
return signal;
}
int
main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;
printf("%%");
while(fgets(buf, MAXLINE, stdin) != NULL)
{
buf[strlen(buf) - 1] = 0;
if((pid = fork()) < 0)
{
printf("fork error");
}
else if(pid == 0)
{
parse_cmd(buf);
printf("could not execute : %s\n", buf);
exit(127);
}
if((pid = waitpid(pid, &status, 0)) < 0)
{
printf("waitpid error");
}
printf("%d\n", status);
printf("%%");
}
exit(0);
}
该程序是书中程序的变种,书中的程序是不能带参数执行命令的,我这里简单的处理了一下参数,这是可独立变异运行的版本,不需要ourhdr.h 头文件的支持。