my_shell 实现

MyShell 实现

一、主要函数

1.fork

被调用一次,有两个返回值。

1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。

注:
1.每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过**getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()**函数获得变量的值。
2.fork的子进程有自己独立的地址空间。不论全局变量或局部变量,子进程与父进程修改互不影响。
3.读时共享,写时复制(实际内存)

2.exec

fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。),也就是这两个进程做完全相同的事。

在fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事)。

实际上,在Linux中并没有exec函数,而是有6个以exec开头的函数族,下表列举了exec函数族的6个成员函数的语法。
在这里插入图片描述
调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段。

exec后新进程保持原进程以下特征:
环境变量(使用了execle、execve函数则不继承环境变量);
Ÿ进程ID和父进程ID;
实际用户ID和实际组ID;
附加组ID;
进程组ID;
会话ID;
控制终端;
当前工作目录;
根目录;
文件权限屏蔽字;
文件锁;
进程信号屏蔽;
未决信号;
资源限制;

3.wait(阻塞)

#include <sys/types.h>
#include <wait.h>
int wait(int *status)

函数功能:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

注:
当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL。
一次wait()只能回收一个。

4.waitpid(非阻塞)

#include <sys/wait.h>
     pid_t
       waitpid(pid_t pid, int *stat_loc, int options);

参数的四种情况:
1.pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
2.pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
3.pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
4.pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options
1.options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项。
2.这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
3.如果我们不想使用它们,也可以把options设为0,如:
ret=waitpid(-1,NULL,0);
4.如果使用了WNOHANG(wait no hung)参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

如果waitpid()函数执行成功,则返回子进程的进程号;如果有错误发生,则返回-1,并且将失败的原因存放在errno变量中。

5.dup / dup2

dup2( int oldfd , int newfd )

dup2可以用参数newfd指定新文件描述符的数值,dup2调用成功,返回新的描述符。(文件描述符0代表标准输入文件,一般就是键盘; 文件描述符1代表标准输出文件,一般是显示器; 文件描述符2代表标准错误输出,一般也指显示器 )

dup2(fd,1)的意思就是把文件描述符替换为1,因为执行命令得到的结果是会向名为1的输出流中输出得到的结果,现在将文件描述符的数值变为1,执行命令后自然就会将结果输入到文件中了。
dup2(fd,0)同理,将文件符的值变为0,命令会从名为0的输入流中读取信息。

二、管道pipe

管道是将前一个命令的输出作为后一个命令的输入

//将|左边的命令执行的结果保存至文件中,在运行|右边的命令时将再将刚刚文件中的内容传入|右边的命令去执行。
fd2 = open("/tmp/youdonotknowfile",O_WRONLY|O_TRUNC|O_CREAT,0644);            //创建临时文件保存管道符前面的操作
dup2(fd2,1);
execvp(arg[0],arg);

fd2 = open("/tmp/youdonotknowfile",O_RDONLY);
dup2(fd2,0);
execvp(argnext[0],argnext);

三、重定向

1.输入重定向

command1 > file1

上面这个命令执行command1然后将输出的内容存入file1。
注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,需使用>>操作符。

2.输出重定向

command1 < file1

这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

3举例

command1 < infile > outfile

同时替换输入和输出,执行command1,从文件infile读取内容,然后将输出写入到outfile中。

四、实现代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<dirent.h>

#define normal 0         //一般
#define out_redirect 1       //输出重定向
#define in_redirect 2     //输入重定向
#define have_pipe 3     //命令中有管道


//打印myshell的提示符"myshell$$"
void print_prompt()
{
    printf("myshell@lin ");
}


//获取待执行命令,buf用于存放输入的命令
//命令过长,则终止程序,输入的命令以"\n"作为结束标志
void get_input(char* buf)
{
    int len = 0;
    char ch;

    ch=getchar();
    while(len < 256 && ch != '\n'){
        buf[len++] = ch;
        ch = getchar();
    }

    if(len == 256){
        printf("command id too long \n");
        exit(-1); //输入命令过长则退出程序
    }
    buf[len] = '\n';
    len++;
    buf[len] = '\0';
}


//解析命令,将结果存入list中
void explain_input(char *buf, int *count, char list[100][256])
{
    char *p = buf;
    char *q = buf;
    int number = 0;

    while(1) {
        if (p[0] == '\n')
            break;

        if(p[0] == ' ')
            p++;
        else{
            q = p;
            number = 0;
            while( (q[0] != ' ') && (q[0] != '\n')){
                number++;
                q++;
            }
            strncpy(list [*count], p, number+1);
            list[*count][number] = '\0';
            *count = *count + 1;
            p = q;
        }
    }
}



//分别在当前目录下、/bin、/usr/bin下查找命令的可执行程序
int find_command(char* command)
{
    DIR* dp;
    struct dirent *dirp;
    char* path[ ] = {"./","/bin","/usr/bin",NULL};

    //使当前目录下的程序可运行
    if(strncmp(command,"./",2) == 0)
        command = command + 2;

    //分别在当前目录、/bin、/usr/bin目录下找要执行的程序
    int i = 0;
    while(path[i] != NULL){
        if((dp = opendir(path[i])) == NULL)
            printf("can not open /bin \n");
        while((dirp = readdir(dp)) != NULL){
            if(strcmp(dirp->d_name,command) == 0){
                closedir(dp);
                return 1;
            }
        }
        closedir(dp);
        i++;
    }
    return 0;
}

void do_cmd(int count, char list[100][256])
{
    int flag = 0;
    int how = 0;         //是否含有>/</|
    int background = 0;    //命令中是否有后台运行标识符&
    int status;
    int i;
    int fd;
    char* arg[count+1];
    char* argnext[count+1];
    char* file;
    pid_t pid;

    //输出命令
    for(i = 0; i < count; i++){
        arg[i] = (char*)list[i];
    }
    arg[count] = NULL;

    //查看命令行是否有后台运行符
    for(i=0; i<count; i++){
        if(strncmp(arg[i],"&",1) == 0){
            if(i == count-1){
                background = 1;
                arg[count - 1] = NULL;
                break;
            }
            else{
                printf("wrong command\n");
                return;
            }
        }
    }

    for(i=0; arg[i] != NULL; i++){
        if(strcmp(arg[i],">") == 0){
            flag++;
            how = out_redirect;
            if(arg[i+1] == NULL)
                flag++;
        }
        if(strcmp(arg[i],"<") == 0){
            flag++;
            how = in_redirect;
            if(i == 0)
                flag++;
        }
        if(strcmp(arg[i],"|") ==0){
            flag++;
            how = have_pipe;
            if(arg[i+1] == NULL)
                flag++;
            if(i == 0)
                flag++;
        }
    }
    //flag>1,说明含有多个< > |符号,本程序不支持
    if(flag>1){
        printf("wrong command\n");
        return;
    }
    if(how == out_redirect){
        for(i=0; arg[i] != NULL; i++){
            if(strcmp(arg[i],">") == 0){
                file = arg[i+1];
                arg[i] = NULL;
            }
        }
    }

    if(how == in_redirect){
        for(i=0; arg[i] != NULL; i++){
            if(strcmp(arg[i],"<") == 0){
                file = arg[i+1];
                arg[i] = NULL;
            }
        }
    }

    if(how == have_pipe){
        for(i=0; arg[i] != NULL; i++){
            if(strcmp(arg[i],"|") == 0){
                arg[i] = NULL;
                int j;
                for(j=i+1; arg[j] != NULL; j++){
                    argnext[j-i-1] = arg[j];
                }
                argnext[j-i-1] = arg[j];   //NULL
                break;
            }
        }
    }

    if((pid = fork()) < 0){
        printf("fork error\n");
        return;
    }

    switch(how) {
        case 0:
            //子进程,不含> < |
            if(pid == 0){
                if(!(find_command(arg[0]))){
                    printf("%s : command not found\n",arg[0]);
                    exit(0);
                }
                execvp(arg[0],arg);
                exit(0);
            }
            break;
        case 1:
            //输入命令含有输出重定向 >
            if(pid == 0){
                if(!(find_command(arg[0]))){
                    printf("%s : command not found\n",arg[0]);
                    exit(0);
                }
                fd = open(file,O_RDONLY);
                dup2(fd,1);
                execvp(arg[0],arg);
                exit(0);
            }
            break;
        case 2:
            //输入命令含有输入重定向 <
            if(pid == 0){
                if(!(find_command(arg[0]))){
                    printf("%s : command not found\n",arg[0]);
                    exit(0);
                }
                fd = open(file,O_RDONLY);
                dup2(fd,0);
                execvp(arg[0],arg);
                exit(0);
            }
            break;
        case 3:
            //输出命令含有管道
            //将|左边的命令执行的结果保存至文件中,在运行|右边的命令时将再将刚刚文件中的内容传入|右边的命令去执行。
            if(pid == 0){
                int pid2;
                int status2;
                int fd2;

                if((pid2 = fork()) < 0){              //子进程再次分离一个子进程
                    printf("fork2 error\n");
                    return ;
                }
                else if(pid2 == 0){
                    if(!(find_command(arg[0]))){
                        printf("%s : command not found\n",arg[0]);
                        exit(0);
                    }
                    fd2 = open("/tmp/youdonotknowfile",O_WRONLY|O_TRUNC|O_CREAT,0644);            //创建临时文件保存管道符前面的操作
                    dup2(fd2,1);
                    execvp(arg[0],arg);
                    exit(0);
                }
                if(waitpid(pid2,&status2,0) == -1)                    //确保管道符前的操作先执行完成
                    printf("wait for child process error\n");

                if( !(find_command(argnext[0])) ){
                    printf("%s : command not found\n",arg[0]);
                    exit(0);
                }
                fd2 = open("/tmp/youdonotknowfile",O_RDONLY);
                dup2(fd2,0);
                execvp(argnext[0],argnext);

                if(remove("/tmp/youdonotknowfile"))
                    printf("remove error\n");
                exit(0);
            }
            break;
        default:
            break;
    }

    //若命令中有&,父进程直接返回,不等子进程结束
    if(background == 1){
        printf("[process id %d]\n",pid);
        return ;
    }

    //父进程等待子进程结束
    if(waitpid(pid,&status,0) == -1)
        printf("wait for child process error\n");
}


int main (int argc,char**argv)
{
    int i;
    int count = 0;
    char list[100][256];
    char **arg = NULL;
    char *buf = NULL;

    buf=(char*)malloc(256);
    if(buf == NULL){
        perror("malloc failed");
        exit(-1);
    }

    while(1){
        //将buf所指向的空间清零
        memset(buf,0,256);
        print_prompt();
        get_input(buf);
        //如果输入的命令为exit/logout则退出本程序
        if(strcmp(buf,"exit\n") == 0 || strcmp(buf,"logout\n") == 0)
            break;
        for(i = 0; i < 100; i++){
            list[i][0]='\0';
        }
        count = 0;
        explain_input(buf, &count, list);
        do_cmd(count, list);
    }
    if(buf != NULL){
        free(buf);
        buf = NULL;
    }
    exit(0);
}

注:
1.

void *memset(void *str, int c, size_t n) 

复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

  • str – 指向要填充的内存块。
  • c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
  • n – 要被设置为该值的字符数。

2.在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值