一.进程的创建
*四个被调用的函数
1.fork():允许一个进程创建一个新进程,新的子进程几近于对父进程的翻版,子进程将获得父进程的栈、数据段、堆和执行文本段的拷贝;
2.exit(status):终止一个进程,将进程占用的资源交还给内核,其参数status为一个整型变量表示进程的退出状态,父进程可以用系统调用wait()来获取该状态;
3.wait(&status):如果子进程尚未调用exit()终止,那么wait()会挂起父进程直至子进程终止,子进程的终止状态会通过status参数返回;
4.execve(pathname,argv,envp):加载一个新程序到当前进程的内存,这将丢弃现存的程序文本段,并为新程序重新创建栈、数据段以及堆;
在fork(),exit(),wait(),execve()的协同使用中,对execve()的调用并非必须。有时,让子进程继续执行与父进程相同的程序反而会有妙用。最终,两种情况殊途同归:总是要通过调用 exit()(或接收一个信号)来终止子进程,而父进程可调用 wait()来获取其终止状态
同样,对 wait()的调用也属于可选项。父进程可以对子进程不闻不问,继续我行我素。不过,由后续内容可知,对 wait()的使用通常也是不可或缺的,每每在 SIGCHLD 信号的处理程序中使用。当子进程终止时,内核会为其父进程产生此类信号
*fork()函数详解
理解 fork()的诀窍是,要意识到,完成对其调用后将存在两个进程,且每个进程都会从 fork()
的返回处继续执行。
这两个进程将执行相同的程序文本段,但却各自拥有不同的栈段、数据段以及堆段拷贝。
子进程的栈、数据以及栈段开始时是对父进程内存相应各部分的完全复制。执行 fork()之后,每个进程均可修改各自的栈数据、以及堆段中的变量,而并不影响另一进程。
程序代码则可通过 fork()的返回值来区分父、子进程。
在父进程中,fork()将返回新创建子进程的进程 ID。鉴于父进程可能需要创建,进而追踪多个子进程(通过 wait()或类似方法),这种安排还是很实用的。
而 fork()在子进程中则返回 0。如有必要,子进程可调用 getpid()以获取自身的进程 ID,调用 getppid()以获取父进程 ID。
当无法创建子进程时,fork()将返回-1。失败的原因可能在于,进程数量要么超出了系统针对此真实用户(real user ID)在进程数量上所施加的限制,要么是触及允许该系统创建的最大进程数这一系统级上限。
执行fork()时,子进程会获得父进程所有的文件描述符的副本。对于shell来说,shell创建子进程后会调用wait()来暂停运行,并等待子进程退出,只有当执行命令的子进程退出后,shell才会打印自己的提示符。
从概念上说来,可以将 fork()认作对父进程程序段、数据段、堆段以及栈段创建拷贝。但是fork()之后常常伴随着 exec(), 这会用新程序替换进程的代码段,并重新初始化其数据段、堆段和栈段。如今的UNIX实现是以以下的俩种方式避免这种浪费的:
-
内核(Kernel)将每一进程的代码段标记为只读,从而使进程无法修改自身代码。这
样,父、子进程可共享同一代码段。系统调用 fork()在为子进程创建代码段时,其所
构建的一系列进程级页表项(page-table entries)均指向与父进程相同的物理内存页帧 -
对于父进程数据段、堆段和栈段中的各页,内核采用写时复制(copy-on-write)技术来处理。最初,内核做了一些设置,令这些段的页表项指向与父进程相同的物理内存页,并将这些页面自身标记为只读。调用 fork()之后,内核会捕获所有父进程或子进程针对这些页面的修改企图,并为将要修改的(about-to-be-modified)页面创建拷贝。系统将新的页面拷贝分配给遭内核捕获的进程,还会对子进程的相应页表项做适当调整。从这一刻起,父、子进程可以分别修改各自的页拷贝,不再相互影响。
BSD后期版本引入了vfork()系统调用,是为子进程立即执行exec()的程序而专门设计的,其效率远高于fork()。不过现代UNIX采用写时复制技术来实现fork(),因而vfork()实际已无存在的必要。鉴于vfork()的怪异语义可能导致一些难以觉察的程序bug,除非能给性能带来重大提升,否则应当尽量避免使用。
vfork()产生的子进程将使用父进程内存,直至其调用exec()或退出,与此同时将会挂起父进程。
*总结
系统调用 fork()通过复制一个与调用进程(父进程)几乎完全一致的拷贝来创建一个新进程(子进程)。系统调用 vfork()是一种更为高效的 fork()版本,不过因为其语义独特—vfork()产生的子进程将使用父进程内存,直至其调用 exec()或退出;于此同时,将会挂起(suspended)父进程,所以应尽量避免使用。
调用 fork()之后,不应对父、子进程获得调度以使用 CPU 的先后顺序有所依赖。对执行顺序做出假设的程序易于产生所谓“竞争条件”的错误。由于此类错误的产生依赖于诸如系统负载之类的外部因素,故而其发现和调试将非常困难
二.进程的终止
程序一般不会直接调用系统调用_exit(),而是调用库函数exit(),它会在执行_exit()前执行各种动作:
- 调用退出处理函数(通过atexit()和on_exit()注册的函数),其执行顺序与注册顺序相反;
- 刷新stdio流缓冲区;
- 使用status提供的值执行_exit()系统调用;
程序的另一种终止方法是从main()函数中返回,或者执行到main()函数的结尾处。执行return n等同于执行对exit(n)的调用,因为main()的运行时函数会将main()的返回值作为exit()的参数。
如果程序直接调用_exit()或因信号而异常终止,则不会调用退出处理程序。
通过fork()创建的子进程会继承父进程注册的退出处理函数,而进程调用exec()时,会移除所有已注册的退出处理函数。
在创建子进程的应用中典型的情况下仅有一个进程(一般为父进程)应通过调用exit()终止,而其他进程应通过调用_exit()终止,从而确保只有一个进程调用退出处理程序并刷新stdio缓冲区。
三.监控子进程
系统调用wait(status)执行如下动作:
- 如果调用进程并无之前未被等待的子进程终止,调用将一直阻塞,直至某个子进程终止。如果调用时已有子进程终止,wait()则立即返回;
- 如果status非空,那么关于子进程如何终止的信息则会通过status指向的整型变量返回;
- 内核将会为父进程下所有子进程的运行总量追加CPU时间以及资源使用数据;
- 将终止子进程的ID作为wait()的结果返回; 出错时wait()返回-1,可能的错误原因之一是调用进程并无之前被等待的子进程。
wait()存在诸多限制,而设计waitpid()则意在突破这些限制:
- 如果父进程已经创建了多个子进程,使用wait()将无法等待某个特定子进程的完成,只能按顺序等待下一个子进程的终止;
- 如果没有子进程退出,wait()总是保持阻塞状态,有时会希望执行非阻塞的等待,是否有子进程退出立即可知;
- 使用wait()只能发现那些已经终止的子进程,对于子进程因某个信号而停止或是已停止子进程收到SIGCONT信号后恢复执行的情况就无能为力了;
- waitid()提供了waitpid()所没有的扩展功能,对于应该等待的子进程事件,waitid()可以更为精确地控制:可通过在options中指定一个或多个标识符来实现这种控制。
wait3()和wait4()执行与waitpid()类似的工作,主要的语义差别在于wait3()和wait4()在参数rusage所指向的结构中返回终止子进程的资源使用情况,包括进程使用的CPU时间总量以及内存管理的统计数据。
父进程与子进程的生命周期一般都不相同,父子进程间互有长短。如果父进程先结束,子进程将变为孤儿进程,并会被init进程接管。如果在父进程wait()之前其子进程就已经终止,系统仍然允许其父进程在之后的某一时刻去执行wait()以确定该子进程是如何终止的:内核通过将该子进程转为僵尸进程来处理这种情况,意味着将释放子进程所把持的大部分资源以供其他进程使用。该进程所唯一保留的是内核进程表中的一条记录,其中记录了子进程的ID、终止状态、资源使用数据等信息。当父进程执行wait()后,由于不再需要子进程所剩余的最后信息,故而内核将删除僵尸进程,如果父进程未执行wait()即退出,那么init进程将接管子进程并自动调用wait(),从而从系统中移除僵尸进程。
父进程应该使用wait()来防止僵尸进程的积累。
如果父进程创建了某一子进程,但并未执行wait(),那么在内核的进程表中将为该子进程永久保留一条记录,如果存在大量此类僵尸进程,它们势必填满内核进程表,从而阻碍新进程的创建。因为僵尸进程无法通过信号杀死,从系统中移除它们的唯一方法就是杀掉它们的父进程,由init进程接管和等待这些僵尸进程,从而清除它们。
无论一个子进程何时终止,系统都会向其父进程发送SIGCHLD信号,对该信号的默认处理是将其忽略,不过也可以安装信号处理程序来捕获并调用wait()来处理僵尸进程。
四.详述进程创建和程序执行
调用execve()之后,因为同一进程仍然存在,所以进程ID保持不变。由于是将调用程序取而代之,对execve()的成功调用将永不返回,而且也无需检查execve()的返回值,因为该值肯定为-1.实际上一旦函数返回,就表明发生了错误。
UNIX内核运行解释器脚本的方式与二进制程序无异,前提是脚本必须满足下面两点要求:
必须赋予脚本文件可执行权限;
文件的起始行必须指定运行脚本解释器的路径名,例如:#! /bin/sh exec()如果检测到传入的文件以#!这两个字节开始,就会析取该行的剩余部分(路径名、参数),并执行解释器程序。
由exec()的调用程序所打开的所有文件描述符在exec()的执行过程中会保持打开状态,且在新程序中依然有效。shell可以利用这一特性为其所执行的程序处理I/O重定向。
第28章 详述进程创建和程序执行
打开进程记账功能后,内核会在每个进程终止时将一条记账信息写入系统级的进程记账文件,这条账单记录包含了内核为该进程所维护的多种信息,包括终止状态以及进程消耗的CPU时间。自内核2.6.10开始,只有当最后一个线程退出时才会为整个进程保存一条账单记录。如果进程的信息并未由其父进程进行监控和报告,那么就可以使用进程记账来获取。
特权进程可以使用系统调用acct()来打开和关闭进程记账功能,应用程序很少使用这一系统调用。
如果系统崩溃,则不会为当前运行的进程记录任何记账信息。
如果开启进程记账特性,且磁盘空闲空间低于low-water百分比,将暂停记账,如果磁盘空间升至low-water之上,则恢复记账。
Linux特有的clone()系统调用也能创建一个新进程,相比于fork(),clone()在进程创建期间对步骤的控制更为精确(通过各种位掩码的组合来指定),clone()主要用于线程库的实现。与fork()不同的是,clone()生成的子进程继续运行时不以调用处为起点,而是调用以参数func所指定的函数。
clone产生的子进程对调用进程的内存既可以获取,也可以共享。但是不能使用父进程的栈,调用者必须为子进程分配一块大小适中的内存空间供子进程的栈使用。
对术语“线程”和“进程”的区分不过是在玩弄文字游戏,实际上线程和进程都是内核调度实体(KSE,kernel scheduling entity),只是与其它KSE之间对属性的共享程度不同(虚拟内存、打开的文件描述符、对信号的处置、进程的ID等)。实际上针对线程间属性共享的方案不少,POSIX线程规范只是其中之一。
POSIX标准规定进程的所有线程共享同一进程ID(即每个线程调用getpid()都应返回相同值),Linux从2.4开始引入了线程组,线程组就是共享同一线程组标识(TGID)的一组KSE,getpid()所返回的就是调用者的TGID,即TGID和进程ID是一回事。
一个线程组内的每个线程都拥有一个唯一的线程标识符用以标识自身,线程可以通过gettid()来获取自己的线程ID。线程ID与进程ID使用相同的数据类型pid_t来表示,线程ID在整个系统中是唯一的,且除了线程担当进程中线程组首线程的情况之外,内核能够保证系统中不会出现线程ID和进程ID相同的情况。
线程组中首个线程的线程ID与线程组ID相同,也将该线程称为线程组首线程。线程组中的所有线程拥有同一个父进程ID,即与线程组首线程ID相同。
如果一个线程组中的任一线程调用了exec(),那么除了首线程之外的其他线程都会终止,新进程将在首线程中执行。
如果线程组中的某个线程调用fork()或vfork()创建了子进程,那么组中的任何线程都可以使用wait()或类似函数来监控该子进程。
容器是轻量级虚拟化的一种形式,将运行于同一内核的进程组从环境上彼此隔离,如同运行在不同的机器上一样。容器可以嵌套,一个容器可以包含另一个容器。为实现容器,内核开发者不得不为内核中的各种全局系统资源提供一个间接层,以便每个容器能够为这些资源提供各自的实例。这些资源包括:进程ID、网络协议栈、uname()返回的ID、用户和组ID命名空间等等。
好的,基础知识已经了解,废话不多说直接上代码
myshell.h
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <dirent.h>
#include <signal.h>
#define IN 1
#define OUT 0
#define MAX_CMD_NUM 4096
#define BUFFSIZE 4096
#define MAX_CMD_LEN 4096
#define USERMODE 0644
int argc; // 有效参数个数
char *argv[MAX_CMD_NUM]; // 参数数组
char command[MAX_CMD_NUM][MAX_CMD_LEN]; // 参数数组
char buf[BUFFSIZE]; // 接受输入的参数数组
char backupBuf[BUFFSIZE]; // 参数数组的备份
char curPath[BUFFSIZE]; // 当前shell工作路径
int i, j; // 循环变量
int commandNum; // 已经输入指令数目
char history[MAX_CMD_NUM][BUFFSIZE]; // 存放历史命令
int getinput(char buf[]); // 输入指令并存入buf数组
void analyse(char *buf); // 解析字符串
void docmd(int argc, char *argv[]); //选择器
int mycd(int argc); // 执行cd指令
int dohistorycmd(char command[MAX_CMD_NUM][MAX_CMD_LEN]); // 打印历史指令
int OutputRedi(char buf[BUFFSIZE]); // 执行输出重定向
int InputRedi(char buf[BUFFSIZE]); // 执行输入重定向命令
int ReOutputRedi(char buf[BUFFSIZE]); // 执行输出重定向追加写
int Pipe(char buf[BUFFSIZE]); // 执行管道命令
int Background(char buf[BUFFSIZE]); // 执行后台运行命令
void signalhander(); // 屏蔽ctrl c信号
myshell.c
#include "myshell.h"
/* 函数定义 */
/* get_input接受输入的字符并存入buf数组中 */
int getinput(char buf[])
{
// buf数组初始化
memset(buf, 0x00, BUFFSIZE);
memset(backupBuf, 0x00, BUFFSIZE);
if (fgets(buf, BUFFSIZE, stdin) == 0)
exit(0);
// 去除fgets带来的末尾\n字符
buf[strlen(buf) - 1] = '\0';
return strlen(buf);
}
void analyse(char *buf)
{
// 初始化argv数组和argc
for (i = 0; i < MAX_CMD_NUM; i++)
{
argv[i] = NULL;
for (j = 0; j < MAX_CMD_LEN; j++)
command[i][j] = '\0';
}
argc = 0;
// 下列操作改变了buf数组, 为buf数组做个备份
strcpy(backupBuf, buf);
/** 构建command数组
* 即若输入为"ls -a"
* strcmp(command[0], "ls") == 0 成立且
* strcmp(command[1], "-a") == 0 成立
*/
int len = strlen(buf);
for (i = 0, j = 0; i < len; ++i)
{
if (buf[i] != ' ')
{
command[argc][j++] = buf[i];
}
else
{
if (j != 0)
{
command[argc][j] = '\0';
++argc;
j = 0;
}
}
}
if (j != 0)
{
command[argc][j] = '\0';
}
/** 构建argv数组
* 即若输入buf为"ls -a"
* strcmp(argv[0], "ls") == 0 成立且
* strcmp(argv[1], "-a") == 0 成立*/
argc = 0;
int flg = OUT;
for (i = 0; buf[i] != '\0'; i++)
{
if (flg == OUT && !isspace(buf[i]))
{
flg = IN;
argv[argc++] = buf + i;
}
else if (flg == IN && isspace(buf[i]))
{
flg = OUT;
buf[i] = '\0';
}
}
argv[argc] = NULL;
}
void docmd(int argc, char *argv[])
{
pid_t pid;
/* 识别program命令 */
// 识别重定向输出命令
for (j = 0; j < MAX_CMD_NUM; j++)
{
if (strcmp(command[j], ">") == 0)
{
strcpy(buf, backupBuf);
int sample = OutputRedi(buf);
return;
}
}
// 识别输入重定向
for (j = 0; j < MAX_CMD_NUM; j++)
{
if (strcmp(command[i], "<") == 0)
{
strcpy(buf, backupBuf);
int sample = InputRedi(buf);
return;
}
}
// 识别追加写重定向
for (j = 0; j < MAX_CMD_NUM; j++)
{
if (strcmp(command[j], ">>") == 0)
{
strcpy(buf, backupBuf);
int sample = ReOutputRedi(buf);
return;
}
}
// 识别管道命令
for (j = 0; j < MAX_CMD_NUM; j++)
{
if (strcmp(command[j], "|") == 0)
{
strcpy(buf, backupBuf);
int sample = Pipe(buf);
return;
}
}
// 识别后台运行命令
for (j = 0; j < MAX_CMD_NUM; j++)
{
if (strcmp(command[j], "&") == 0)
{
strcpy(buf, backupBuf);
int sample = Background(buf);
return;
}
}
/* 识别shell内置命令 */
if (strcmp(command[0], "cd") == 0)
{
int res = mycd(argc);
if (!res)
printf("cd指令输入错误!");
}
else if (strcmp(command[0], "history") == 0)
{
dohistorycmd(command);
}
else if (strcmp(command[0], "exit") == 0)
{
exit(0);
}
else
{
switch (pid = fork())
{
// fork子进程失败
case -1:
printf("创建子进程未成功");
return;
// 处理子进程
case 0:
{
/* 函数说明:execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件,
* 然后将第二个参数argv 传给该欲执行的文件。
* 返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中.
* */
execvp(argv[0], argv);
// 代码健壮性: 如果子进程未被成功执行, 则报错
printf("%s: 命令输入错误\n", argv[0]);
// exit函数终止当前进程, 括号内参数为1时, 会像操作系统报告该进程因异常而终止
exit(1);
}
default:
{
int status;
waitpid(pid, &status, 0); // 等待子进程返回
int err = WEXITSTATUS(status); // 读取子进程的返回码
if (err)
{
printf("Error: %s\n", strerror(err));
}
}
}
}
}
int mycd(int argc)
{
// result为1代表执行成功, 为0代表执行失败
int result = 1;
if (argc != 2)
{
printf("指令数目错误!");
}
else
{
int ret = chdir(command[1]);
if (ret)
return 0;
}
if (result)
{
char *res = getcwd(curPath, BUFFSIZE);
if (res == NULL)
{
printf("文件路径不存在!");
}
return result;
}
return 0;
}
int dohistorycmd(char command[MAX_CMD_NUM][MAX_CMD_LEN])
{
int n = atoi(command[1]);
for (i = n; i > 0 && commandNum - i >= 0; i--)
{
printf("%d\t%s\n", n - i + 1, history[commandNum - i]);
}
return 0;
}
int OutputRedi(char buf[BUFFSIZE])
{
strcpy(buf, backupBuf);
char outFile[BUFFSIZE];
memset(outFile, 0x00, BUFFSIZE);
int RediNum = 0;
for (i = 0; i + 1 < strlen(buf); i++)
{
if (buf[i] == '>' && buf[i + 1] == ' ')
{
RediNum++;
break;
}
}
if (RediNum != 1)
{
printf("输出重定向指令输入有误!");
return 0;
}
for (i = 0; i < argc; i++)
{
if (strcmp(command[i], ">") == 0)
{
if (i + 1 < argc)
{
strcpy(outFile, command[i + 1]);
}
else
{
printf("缺少输出文件!");
return 0;
}
}
}
// 指令分割, outFile为输出文件, buf为重定向符号前的命令
for (j = 0; j < strlen(buf); j++)
{
if (buf[j] == '>')
{
break;
}
}
buf[j - 1] = '\0';
buf[j] = '\0';
// 解析指令
analyse(buf);
pid_t pid;
switch (pid = fork())
{
case -1:
{
printf("创建子进程未成功");
return 0;
}
// 处理子进程:
case 0:
{
// 完成输出重定向
int fd;
fd = open(outFile, O_WRONLY | O_CREAT | O_TRUNC, USERMODE);
// 文件打开失败
if (fd < 0)
{
exit(1);
}
dup2(fd, STDOUT_FILENO);
execvp(argv[0], argv);
if (fd != STDOUT_FILENO)
{
close(fd);
}
// 代码健壮性: 如果子进程未被成功执行, 则报错
printf("%s: 命令输入错误\n", argv[0]);
// exit函数终止当前进程, 括号内参数为1时, 会像操作系统报告该进程因异常而终止
exit(1);
}
default:
{
int status;
waitpid(pid, &status, 0); // 等待子进程返回
int err = WEXITSTATUS(status); // 读取子进程的返回码
if (err)
{
printf("Error: %s\n", strerror(err));
}
}
}
}
int InputRedi(char buf[BUFFSIZE])
{
strcpy(buf, backupBuf);
char inFile[BUFFSIZE];
memset(inFile, 0x00, BUFFSIZE);
int RediNum = 0;
for (i = 0; i + 1 < strlen(buf); i++)
{
if (buf[i] == '<' && buf[i + 1] == ' ')
{
RediNum++;
break;
}
}
if (RediNum != 1)
{
printf("输入重定向指令输入有误!");
return 0;
}
for (i = 0; i < argc; i++)
{
if (strcmp(command[i], "<") == 0)
{
if (i + 1 < argc)
{
strcpy(inFile, command[i + 1]);
}
else
{
printf("缺少输入指令!");
return 0;
}
}
}
// 指令分割, InFile为输出文件, buf为重定向符号前的命令
for (j = 0; j < strlen(buf); j++)
{
if (buf[j] == '<')
{
break;
}
}
buf[j - 1] = '\0';
buf[j] = '\0';
analyse(buf);
pid_t pid;
switch (pid = fork())
{
case -1:
{
printf("创建子进程未成功");
return 0;
}
// 处理子进程:
case 0:
{
// 完成输入重定向
int fd;
fd = open(inFile, O_RDONLY, USERMODE);
// 文件打开失败
if (fd < 0)
{
exit(1);
}
dup2(fd, STDIN_FILENO);
execvp(argv[0], argv);
if (fd != STDIN_FILENO)
{
close(fd);
}
// 代码健壮性: 如果子进程未被成功执行, 则报错
printf("%s: 命令输入错误\n", argv[0]);
// exit函数终止当前进程, 括号内参数为1时, 会像操作系统报告该进程因异常而终止
exit(1);
}
default:
{
int status;
waitpid(pid, &status, 0); // 等待子进程返回
int err = WEXITSTATUS(status); // 读取子进程的返回码
if (err)
{
printf("Error: %s\n", strerror(err));
}
}
}
}
int ReOutputRedi(char buf[BUFFSIZE])
{
strcpy(buf, backupBuf);
char reOutFile[BUFFSIZE];
memset(reOutFile, 0x00, BUFFSIZE);
int RediNum = 0;
for (i = 0; i + 2 < strlen(buf); i++)
{
if (buf[i] == '>' && buf[i + 1] == '>' && buf[i + 2] == ' ')
{
RediNum++;
break;
}
}
if (RediNum != 1)
{
printf("追加输出重定向指令输入有误!");
return 0;
}
for (i = 0; i < argc; i++)
{
if (strcmp(command[i], ">>") == 0)
{
if (i + 1 < argc)
{
strcpy(reOutFile, command[i + 1]);
}
else
{
printf("缺少输出文件!");
return 0;
}
}
}
// 指令分割, outFile为输出文件, buf为重定向符号前的命令
for (j = 0; j + 2 < strlen(buf); j++)
{
if (buf[j] == '>' && buf[j + 1] == '>' && buf[j + 2] == ' ')
{
break;
}
}
buf[j - 1] = '\0';
buf[j] = '\0';
// 解析指令
analyse(buf);
pid_t pid;
switch (pid = fork())
{
case -1:
{
printf("创建子进程未成功");
return 0;
}
// 处理子进程:
case 0:
{
// 完成输出重定向
int fd;
fd = open(reOutFile, O_WRONLY | O_APPEND | O_CREAT | O_APPEND, USERMODE);
// 文件打开失败
if (fd < 0)
{
exit(1);
}
dup2(fd, STDOUT_FILENO);
execvp(argv[0], argv);
if (fd != STDOUT_FILENO)
{
close(fd);
}
// 代码健壮性: 如果子进程未被成功执行, 则报错
printf("%s: 命令输入错误\n", argv[0]);
// exit函数终止当前进程, 括号内参数为1时, 会像操作系统报告该进程因异常而终止
exit(1);
}
default:
{
int status;
waitpid(pid, &status, 0); // 等待子进程返回
int err = WEXITSTATUS(status); // 读取子进程的返回码
if (err)
{
printf("Error: %s\n", strerror(err));
}
}
}
}
int Pipe(char buf[BUFFSIZE])
{
// 获取管道符号的位置索引
for (j = 0; buf[j] != '\0'; j++)
{
if (buf[j] == '|')
break;
}
// 分离指令, 将管道符号前后的指令存放在两个数组中
// outputBuf存放管道前的命令, inputBuf存放管道后的命令
char outputBuf[j];
memset(outputBuf, 0x00, j);
char inputBuf[strlen(buf) - j];
memset(inputBuf, 0x00, strlen(buf) - j);
for (i = 0; i < j - 1; i++)
{
outputBuf[i] = buf[i];
}
for (i = 0; i < strlen(buf) - j - 1; i++)
{
inputBuf[i] = buf[j + 2 + i];
}
int pd[2];
pid_t pid;
if (pipe(pd) < 0)
{
perror("pipe()");
exit(1);
}
pid = fork();
if (pid < 0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
// 子进程写管道
close(pd[0]); // 关闭子进程的读端
dup2(pd[1], STDOUT_FILENO); // 将子进程的写端作为标准输出
analyse(outputBuf);
execvp(argv[0], argv);
if (pd[1] != STDOUT_FILENO)
{
close(pd[1]);
}
}
else
{
// 父进程读管道
/** 关键代码
* 子进程写管道完毕后再执行父进程读管道, 所以需要用wait函数等待子进程返回后再操作
*/
int status;
waitpid(pid, &status, 0); // 等待子进程返回
int err = WEXITSTATUS(status); // 读取子进程的返回码
if (err)
{
printf("Error: %s\n", strerror(err));
}
close(pd[1]); // 关闭父进程管道的写端
dup2(pd[0], STDIN_FILENO); // 管道读端读到的重定向为标准输入
analyse(inputBuf);
execvp(argv[0], argv);
if (pd[0] != STDIN_FILENO)
{
close(pd[0]);
}
}
return 1;
}
int Background(char buf[BUFFSIZE])
{
char backgroundBuf[strlen(buf)];
memset(backgroundBuf, 0x00, strlen(buf));
for (i = 0; i < strlen(buf); i++)
{
backgroundBuf[i] = buf[i];
if (buf[i] == '&')
{
backgroundBuf[i] = '\0';
backgroundBuf[i - 1] = '\0';
break;
}
}
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
// 将stdin、stdout、stderr重定向到/dev/null
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "r", stdin);
signal(SIGCHLD, SIG_IGN);
analyse(backgroundBuf);
execvp(argv[0], argv);
printf("%s: 命令输入错误\n", argv[0]);
// exit函数终止当前进程, 括号内参数为1时, 会像操作系统报告该进程因异常而终止
exit(1);
}
else
{
exit(0);
}
}
void signalhander()
{
;
}
/* main函数 */
int main()
{
// while循环
while (1)
{
signal(SIGINT, signalhander);
printf("[myshell]$ ");
// 输入字符存入buf数组, 如果输入字符数为0, 则跳过此次循环
if (getinput(buf) == 0)
continue;
strcpy(history[commandNum++], buf);
strcpy(backupBuf, buf);
analyse(buf);
docmd(argc, argv);
argc = 0;
}
}