4. 贯穿案例2:mini shell(3)

(1)之前存在问题

  ①刚运行时,mshell作为前台进程。运行的其他命令会被加入新的进程组,并且调用tcsetpgrp将这个进程组设置为前台进程组,因此mshell本身所在的进程组就成为后台进程组

  ②SIGTTIN信号表示后台进程组的成员读控制终端时会产生的信号。而SIGTTOU信号表示后台进程组的成员写控制终端是产生的信号这两个信号的默认操作是暂停进程。因此mshell上运行一些命令后,处于后台进程的mshell会试图返回到提示符状态下,这将导致mshell试图读写控制终端,从而造成程序被暂停

(2)解决方案:忽略或捕获SIGTTIN和SIGTTOU信号

【编程实验】mini shell

//job.h

#ifndef __JOB_H__
#define __JOB_H__
#include <sys/types.h> //重定向类型(这里只支持3种,<、>和>>)
enum RedirectType{RedirectRead, RedirectWrite, RedirectAppend};
typedef struct
{
enum RedirectType redirect; //重定向的类型
int fd; //将标准输入、输出重定向到fd这个目标文件
}Redirection; //接收命令行参数
typedef struct
{
pid_t pid; //进程pid
char** args; //对应于主函数中的char* argv[]参数 //每个命令允许使用多个重定向符号,放在以下数组中
//如:echo aaa>s.txt bbb>>s.txt
Redirection* redirects; //堆上申请的数组
int redirect_num;
}Program; //命令行中可以包含多个程序,如
//#date;ls -l,单个或多个命令通过cmd传入Job结构体中
typedef struct
{
char* cmd; //单条命令或多条命令(用分号隔开)
int progs_num; //作业中包含程序的数量
Program* progs; //各个程序的命令行参数
pid_t pgid; //进程组ID,设置前(后)台进程
}Job; //创建作业
extern Job* create_job(char* cmd);
//销毁作业
extern void destroy_job(Job* job);
//创建进程(命令)
extern Program* create_program(char** arg);
//销毁进程(命令)
extern void destroy_program(Program* prog);
//将命令加入作业中
extern int add_program(Job* job, Program* prog); extern Redirection* create_redirect(int fd, enum RedirectType type);
extern void destroy_redirect(Redirection* r);
extern void add_redirection(Program* prog, Redirection* r); #endif

//job.c

#include "job.h"
#include <malloc.h>
#include <assert.h>
#include <string.h> //创建作业
Job* create_job(char* cmd)
{
Job* job = (Job*)malloc(sizeof(Job));
assert( job != NULL); job->cmd = (char*)malloc(sizeof(char) * strlen(cmd));
assert(job->cmd != NULL);
strcpy(job->cmd, cmd); job->progs_num = ;
job->progs = NULL; return job;
} //销毁作业
void destroy_job(Job* job)
{
assert(job != NULL);
free(job->progs);
free(job->cmd);
free(job);
} //arg格式:command arg0 arg1 ==> 返回3
static int arg_num(char** arg)
{
int ret = ;
char* start = arg[]; while(start != NULL){
start = arg[++ret];
} return ret;
} //创建进程(命令)
Program* create_program(char** arg)
{
Program* prog = (Program*)malloc(sizeof(Program));
assert(prog != NULL); prog->redirect_num = ;
prog->redirects = NULL; int counter = arg_num(arg);
prog->args = (char**)calloc(counter + , sizeof(char*)); //以NULL结尾 int i = ;
for(i=; i< counter; i++){
int len = strlen(arg[i]);
prog->args[i] = (char*)malloc(len);
assert(prog->args[i] != NULL); strcpy(prog->args[i], arg[i]);
} prog->args[i] = NULL; //指针数组,以NULL结尾 return prog;
} //销毁进程(命令)
void destroy_program(Program* prog)
{
assert(prog != NULL); int i = ;
while(prog->args[i] != NULL)
{
free(prog->args[i++]);
} free(prog->redirects);
free(prog->args);
free(prog);
} //将命令加入作业中
int add_program(Job* job, Program* prog)
{
//重新申请一片空间以增加一条命令进来,放入job->progs中
Program* ps = (Program*)malloc(sizeof(Program) * (job->progs_num + ));
memcpy(ps, job->progs, job->progs_num * sizeof(Program)); ps[job->progs_num++] = *prog;//将新的进程(命令)加入进来 free(job->progs); //释放旧的程序(命令)组
job->progs = ps; return job->progs_num - ; //返回新命令的索引号
} Redirection* create_redirect(int fd, enum RedirectType type)
{
Redirection* r = (Redirection*)calloc(, sizeof(Redirection));
assert( r!= NULL); r->fd = fd;
r->redirect = type; return r;
} void destroy_redirect(Redirection* r)
{
assert( r!= NULL);
free(r);
} void add_redirection(Program* prog, Redirection* r)
{
Redirection* rs = (Redirection*)calloc(prog->redirect_num + ,
sizeof(Redirection));
assert(rs != NULL);
//复制原有数据
if(prog->redirects != NULL){
memcpy(rs, prog->redirects, prog->redirect_num* sizeof(Redirection));
free(prog->redirects); //释放原有的redirects
} prog->redirects = rs; //将新的redirection加入数组中
memcpy(&prog->redirects[prog->redirect_num], r, sizeof(Redirection)); prog->redirect_num += ;
}

//mshell.c

#include "job.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <sys/wait.h> char* prompt = "mshell> "; //命令行的提示符
#define MAX_COMMAND_LEN 256 //命令行最多的字符数
extern char** environ; //环境表指针 //前台和后台进程组标志
#define FOREGROUND 0 //前台进程组
#define BACKGROUND 1 //后台进程组 //env命令
void env_fun(void)
{
int i = ;
char* env = NULL;
while((env = environ[i++]) != NULL){
printf("%s\n", env);
}
} //export命令
void export_fun(Program* prog)
{
//export格式:export CITY=ShangHai
if(prog->args[] == NULL){
fprintf(stderr, "export: invalid argument\n");
return;
} putenv(prog->args[]);
} //echo命令
void echo_fun(Program* prog)
{
char* s = prog->args[];
if(s == NULL){
fprintf(stderr, "echo: invalid argument\n");
return;
}
//echo的格式:echo $PATH
//1. echo $PATH 从环境变量中读取
//2. echo PATH 直接输出echo后面的字符,如“PATH”
if(s[] == '$'){
char* v = getenv(s + );
printf("%s\n", v);
}else{
printf("%s\n", s);
}
} //cd命令
void cd_fun(Program* prog)
{
if(chdir(prog->args[]) < ){
perror("cd error");
}
} //pwd命令
void pwd_fun(Program* prog)
{
char buffer[];
memset(buffer, , sizeof(buffer)); if(getcwd(buffer, sizeof(buffer)) == NULL){
perror("pwd error");
} printf("%s\n", buffer);
} //分析命令所带的参数(含进程名本身)
void split_cmd(Job* job, char* arguments, int* bg)
{
char** args = (char**)calloc(MAX_COMMAND_LEN, sizeof(char*));
assert( args != NULL); char* cmd = strtok(arguments, " "); //1. 先取出命令名本身 args[] = (char*)calloc(strlen(cmd) + , sizeof(char)); //命令本身
strcpy(args[], cmd); Redirection* rs[]; //一条命令中重定向的符号不会太多。为简单起见,假设为5个。
int redirect_num = ; int i = ;
char* s = NULL;
//2. 剩余的为参数部分
*bg = FOREGROUND;
while((s = strtok(NULL, " ")) != NULL){ //将参数分隔出来
if(!strcmp(s, "&")){
//设置后台进程标志
*bg = BACKGROUND;
continue;
} if(!strcmp(s, "<")){ //如果参数<
//格式:cat < s.txt
char* file = strtok(NULL, " "); //重定向“<”后面的为文件名
if(file == NULL){
continue;
}else{
//打开要重定向到的目标文件,因为后面要用dup2来完成重定向
int fd = open(file, O_RDONLY); //输入重定向,只需以只读打开
rs[redirect_num++] = create_redirect(fd, RedirectRead);
} continue;
}; if(!strcmp(s, ">")){
//格式:cat > s.txt
char* file = strtok(NULL, " "); //重定向“>”后面的为文件名
if(file == NULL){
continue;
}else{
//打开要重定向到的目标文件,因为后面要用dup2来完成重定向
//输出重定向,需以可写方式打开
int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, );
rs[redirect_num++] = create_redirect(fd, RedirectWrite);
} continue;
} if(!strcmp(s, ">>")){
//格式:cat >> s.txt
char* file = strtok(NULL, " "); //重定向“>>”后面的为文件名
if(file == NULL){
continue;
}else{
int fd = open(file, O_WRONLY | O_CREAT | O_APPEND, );
rs[redirect_num++] = create_redirect(fd, RedirectAppend);
}
continue;
} args[i] = (char*)calloc(strlen(s)+, sizeof(char));
strcpy(args[i++], s);
} //根据args创建一个Program
Program* prog = create_program(args); int k = ;
for(; k < redirect_num; k++){
add_redirection(prog, rs[k]);//将所有重定向信息放入prog中
destroy_redirect(rs[k]);
} add_program(job, prog); int j = ;
for(j=; j < i; j++){
free(args[j]);
} free(args);
} //多条命令的解析
void parse_cmd(Job* job, char* line, int* bg)
{
char buff [MAX_COMMAND_LEN]={}; //以“;”号分隔多条命令
char* pos = line;
char* start = line;
int count = ;
while( start < (line + strlen(line)) ){ //将参数分隔出来
memset(buff, , sizeof(buff));
if((pos = strchr(pos, ';')) == NULL)
{
pos = line + strlen(line);
} count = pos-start;
if(count > ){
memcpy(buff, start, count);
split_cmd(job, buff, bg);
}
start = ++pos;
}
} //执行命令
void execute_cmd(Job* job, int bg)
{
int i = ;
for(i=; i<job->progs_num; i++)
{
if(!strcmp(job->progs[i].args[], "cd")){ //cd命令
cd_fun(&job->progs[i]);
}else if(!strcmp(job->progs[i].args[], "pwd")){ //pwd命令
pwd_fun(&job->progs[i]);
}else if(!strcmp(job->progs[i].args[], "exit")){ //exit命令
exit();
}else if(!strcmp(job->progs[i].args[], "env")){ //env命令
env_fun();
}else if(!strcmp(job->progs[i].args[], "export")){ //export命令
export_fun(&job->progs[i]);
}else if(!strcmp(job->progs[i].args[], "echo")){ //echo命令
echo_fun(&job->progs[i]);
}else{ //其他命令用exec函数完成
//创建子进程来执行其它命令
pid_t pid;
if((pid = fork()) < ){
perror("fork error");
}else if(pid == ){ //child process //由于子进程继承了父进程的信号处理方式,因此子进程应恢复
//为默认的(如可以被ctrl-c终止,ctrl-z暂停或读写终端等)
signal(SIGTTIN, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGCHLD, SIG_DFL); //第1个子进程创建新的进程组,以后的子进程加入到该组当中
if(i==){
if(setpgid(getpid(), getpid()) < ){ //组长进程
perror("setpgid error!");
}
job->pgid = getpgid(getpid()); //保存组长进程ID
}else{
//其余子进程加入到新的进程组中
if(setpgid(getpid(), job->pgid) < ){
perror("setpgid error");
}
} //设置前台进程组
if(bg == FOREGROUND){
tcsetpgrp(, getpgid(getpid()));
} //对标准输入、标准输出和追加输出进行重定向
int k = ;
job->progs[i].pid = getpid(); //每条命令会启动一个子进程,记录其pid
for(; k<job->progs[i].redirect_num; k++){
if(job->progs[i].redirects[k].redirect == RedirectRead){
//将标准输入重定向到指定的文件中
if(dup2(job->progs[i].redirects[k].fd, STDIN_FILENO) != STDIN_FILENO){
perror("dup2 error");
}
}
if((job->progs[i].redirects[k].redirect == RedirectWrite) ||
(job->progs[i].redirects[k].redirect == RedirectAppend)){
//将标准输出重定向到指定的文件中
if(dup2(job->progs[i].redirects[k].fd, STDOUT_FILENO) != STDOUT_FILENO){
perror("dup2 error");
}
}
} //end for2 //调用exec函数执行系统中的其它命令
if(execvp(job->progs[i].args[], job->progs[i].args) < ){
perror("execvp error");
exit(); //子进程退出
}
}else{ //parent process
if(i == ){ //与子进程执行相同的逻辑,以确保不会因进程调度而出现错误
//由minishell启动的所有子进程默认放到一个新的进程组,组长进程为
//第1个子进程
if ((setpgid(pid, pid)) < ) {
perror("setpgid error");
}
job->pgid = pid;
}else{
//其余子进程加入到新的进程组中去
if((setpgid(pid, job->pgid)) < ){
perror("setpgid error");
}
} if(bg == FOREGROUND){
tcsetpgrp(, job->pgid);
//等待子进程组结束,含曾被暂停过的(阻塞)
waitpid(-job->pgid, NULL, WUNTRACED);
} //后台进程
if(bg == BACKGROUND){
waitpid(-job->pgid, NULL, WNOHANG); //非阻塞
}
} //end fork
}
} //end for1
} //信号处理函数
void sig_handler(int signo)
{
if(signo == SIGCHLD){
//回收进程组中所有的子进程
//每个子进程结束时,父进程都会收到这个信号。
waitpid(-, NULL, WNOHANG); //非阻塞
//将mshell设置为前台进程
tcsetpgrp(, getpgid(getpid()));
}
} int main(int argc, char* argv[])
{
//将mshell本身设置成一个进程组
setpgid(getpid(), getpid()); signal(SIGTTIN, SIG_IGN); //忽略SIGTTIN信号
signal(SIGTTOU, SIG_IGN); //忽略SIGTTOU信号
signal(SIGINT, SIG_IGN); //忽略ctrl-c信号,防止mshell被ctrl-c杀掉
signal(SIGTSTP, SIG_IGN); //忽略ctrl-z,防止mshell被ctrl-z暂停
signal(SIGCHLD, sig_handler); //捕获信号。回收子进程,同时将mshell设为前台进程 char buffer[MAX_COMMAND_LEN];
memset(buffer, , MAX_COMMAND_LEN); ssize_t size = strlen(prompt) * sizeof(char);
write(STDOUT_FILENO, prompt, size); ssize_t len = ;
int bg; //设置前台和后台进程组标志 while(){
len = read(STDIN_FILENO, buffer, MAX_COMMAND_LEN);
buffer[len -] = ; //以NULL结尾 if(strlen(buffer) > ){
Job* job = create_job(buffer); //解析命令
parse_cmd(job, buffer, &bg);
//执行命令
execute_cmd(job, bg); destroy_job(job);
} write(STDOUT_FILENO, prompt, size);
memset(buffer, , MAX_COMMAND_LEN);
} return ;
}

第8章 信号(6)_贯穿案例2:mini shell(3)的更多相关文章

  1. 第7章 进程关系(5)_贯穿案例2:mini shell(2)

    5. 贯穿案例2:mini shell(2) (1)己经完成的功能:pwd.cd.exit命令 (2)阶段性目标: ①env.export.echo及其他命令 ②标准输入.输出重定向"> ...

  2. 第4章 文件和目录(5)_贯穿案例2:mini shell(1)

    6. 贯穿案例2:mini shell(1) [阶段性任务]实现cd.pwd和quit命令 //job.h #ifndef __JOB_H__ #define __JOB_H__ //接收命令行参数 ...

  3. 第3章 文件I/O(8)_贯穿案例:构建标准IO函数库

    9. 贯穿案例:构建标准IO函数库 //mstdio.h #ifndef __MSTDIO_H__ #define __MSTDIO_H__ #include <unistd.h> #de ...

  4. ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区

    原文:ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区 1 入门案例分析 在第一章里,我们已经对ArcGIS系列软件的体系结构有了一 ...

  5. 黑黛增发罗林川:如何三年开1000家连锁店?_深度案例_i黑马

    黑黛增发罗林川:如何三年开1000家连锁店?_深度案例_i黑马 黑黛增发

  6. 第1章 重构,第一个案例(3):运用多态取代switch

    3. 运用多态取代与价格相关的条件逻辑 3.1 switch和“常客积分”代码的再次搬迁 (1)switch:最好不要在另一个对象的属性上运用switch语句 switch(getMovie().ge ...

  7. 第1章 重构,第一个案例(2):分解并重组statement函数

    2. 分解并重组statement (1)提炼switch语句到独立函数(amountFor)和注意事项. ①先找出函数内的局部变量和参数:each和thisAmount,前者在switch语句内未被 ...

  8. 第1章 重构,第一个案例(1):糟糕的statement函数设计

    1. 启航:影片出租,计算每一位顾客的消费金额并打印清单 1.1 场景说明: (1)影片分类规则:普通片.儿童片和新片等3类 (2)每种影片计算租金的方式. ①普通片:基本租金为2元,超过2天的部分每 ...

  9. Python爬虫Scrapy(二)_入门案例

    本章将从案例开始介绍python scrapy框架,更多内容请参考:python学习指南 入门案例 学习目标 创建一个Scrapy项目 定义提取的结构化数据(Item) 编写爬取网站的Spider并提 ...

随机推荐

  1. SWIFT用ScrollView加图片制作Banner

    网上参考OBJC写的用ScrollView图片轮播效果,照着画了个,先上效果图: 附上代码: @IBOutlet weak var pc: UIPageControl! @IBOutlet weak ...

  2. Swift 编程语言入门教程

    1   简介 今天凌晨Apple刚刚发布了Swift编程语言,本文从其发布的书籍<The Swift Programming Language>中摘录和提取而成.希望对各位的iOS& ...

  3. qt5 交叉编译

    qtbase/mkspecs/linux-arm-gnueabi-g++/qmake.conf 添加 QMAKE_LFLAGS += -Wl,-rpath-link,$$[QT_SYSROOT]/us ...

  4. 怎样取消老毛桃软件赞助商---只需在输入框中输入老毛桃官网网址“laomaotao.org”

    来源:www.laomaotao.org 时间:2015-01-29 在众多网友和赞助商的支持下,迄今为止,老毛桃u盘启动盘制作工具已经推出了多个版本.如果有用户希望取消显示老毛桃软件中的赞助商,那不 ...

  5. HPU 1437: 王小二的求值问题

    1437: 王小二的求值问题 时间限制: 1 Sec 内存限制: 128 MB提交: 141 解决: 31 统计 题目描述 题意超级简单,求一个序列的次大值. 输入 多组输入,每个测试实例占两行,包括 ...

  6. 《DSP using MATLAB》Problem 4.3

    代码: %% ------------------------------------------------- %% 1 x(n)=2δ(n-2)+3u(n-3) %% -------------- ...

  7. 给div加滚动条

    <div style="width:175px;height:100px;background:white;overflow:scroll;"> <span> ...

  8. vulcanjs 核心架构概念

    基于包的架构 为了保证系统的灵活以及可扩展,vulcanjs 使用基于包的架构设计,每一个功能都是一个包,可以方便的添加,移除 扩展.而不是修改 vulcan 的设计哲学是进行系统扩展,而不是编辑修改 ...

  9. .NET4.0框架退休日期逐渐临近

    微软宣布了.NET框架4.0至4.5.1版本的生命周期终结计划. 2016年1月12日之后,所有的技术支持,包含安全和非安全的更新补丁,都将会停止.开发人员和用户到时候可以选择回退到.NET 3.5 ...

  10. 使用 telnet 发邮件

    我们都习惯了用邮件客户端软件或者登录到电子邮件站点进行收发邮件,现在尝试一下使用 Windows 自带的 Telnet 程序手工地发送一封简单的邮件,以此来稍微明白关于邮件发送的一些知识. 现在 E- ...