LInux C编程-myshell的实现

  • 在Linux系统中,shell是我们每天经常使用的东西,而如何实现一个自己的shell?首先我们需要了解一些基础知识

一.进程基础知识

进程概述: CPU执行的程序,是一个动态的实体,进程是操作系统资源分配的基本单位。

进程和程序的区别在于进程是动态的,程序是静态的,进程是运行中的程序

linux 下可通过ps命令来实现查看

Linux中一个进程由三部分组成,代码段,数据段和栈堆端。代码段存放程序的全局变量,常量,静态变量。堆栈段中的堆用于存放动态分配的全局变量,堆栈中栈用于函数调用,它存放着函数的参数,函数内部定义的局部变量

Linux中的进程控制,系统提供了一些函数可以使用:

  • fork用于创建一个新进程
  • exit用于终结进程
  • wait将父进程挂起,等待子进程的终结
  • getpid 获取当前进程的PID
  • nice改变进程的优先级

进程分为几种不同的状态,运行状态,可中断状态,不可中断状态,僵尸进程,停止进程

2.还有几种特殊的进程
孤儿进程: 如果一个子进程的父进程先与子进程结束,子进程就成为一个孤儿进程

守护进程:在后台运行的,没有控制终端与之相连的进程,它独立于控制终端,周期性的执行某些任务

二.进程的内存映像

我们在linux平时见到的程序,他们是如何转化为进程的?
通常需要如下几个步骤

  • 内核将程序读入内存,为程序分配内存空间
  • 内核为该进程分配了进程标识符(PID)和其他所需资源
  • 内核为该进程保存了PID及其相应的状态信息,把程序放进队列种等待执行,这样就可以被操作系统的调度程序执行了

2.进程的内存映像
进程的内存映像是指的内核在内存中如何存放可执行程序文件,在将程序转化为进程的过程中,把硬盘复制给内存之中
,而在实现自己的shell中就使用到这一概念,可执行程序位于磁盘中,而内存映像在内存之中,内存映像随着程序的执行在动态变化中

三.实现myshell所涉及到的部分函数的用法

1.fork函数

fork函数是创建子进程的方式.
include< stdio.h>
include < unistd.h>
pid _t fork (void);
使用fork函数可以将当前进程分裂为两个进程,但是不同的是fork函数有两个返回值,一个是父进程调用fork的返回值,一个是子进程中fork函数的返回值

2.vfork函数
vfork函数与fork函数的不同是什么?
他们的基本用法是相同的,也有一些地方有一些不同,

  • vfork和fork一样都是调用一次,返回两次
  • 使用fork创建一个子进程之后,子进程会继承父进程的资源,具有良好的并发性,而vfork创建的子进程会共享父进程的地址空间,子进程对该地址空间中任何数据的修改都会被父进程所看到
  • vfork函数,一般是子进程先执行,之后父进程才进行执行

3.进程退出
LInux中进程退出分为两种,正常退出和异常退出

(1) 正常退出
在main函数中执行return
调用exit函数
调用_exit函数
而exit和_exit函数有什么区别尼,exit函数在结束时会清除缓冲区中的内容,而_exit会交给内核处理,直接关闭
(2) 异常退出
调用abort函数
进程收到某种信号,而信号会让程序停止

注:尽量减少僵尸进程的产生,应该合理的使用wait/waitpid函数,来让父进程等待子进程的结束

4.执行程序
很多小伙伴看到这里应该就很好奇,如何执行进程?
在这里给大家介绍一个函数族 exec族身为地字一号的重要人物,其作用是十分广阔的

原理:使用exec族执行一个可执行的文件来代替当前进程的内存映像
exec的族的使用并没有产生新的进程哦,而是把程序的代码换入,重新分贝数据段和栈堆段.

exec族的成员如下

 #include <unistd.h>
 extern char **environ;
 int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
 int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
  int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
  int execv(const char *path, char *const argv[]);
  int execvp(const char *file, char *const argv[]);
 int execvpe(const char *file, char *const argv[],
                       char *const envp[]);

我在这里使用到了execvp,在这里介绍以下它的用法吧,

它的参数中的filename,如果其中包含了”/”的话,相当于路径,不包含的话”/”,函数就到环境变量中PATH来寻找

4.等待进程结束
这点是必要的,不然会造成僵尸进程的产生

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitint *statloc)
pid_t waitpid (pid_t pid, int * statloc , int options)

wait函数让父进程暂停执行,直到它的一个子进程结束为停止
状态信息将会被写入到statloc
waitpid 函数也用来等待子进程的结束,但是它有特定的要求pid需指定要等待的子进程的pid

5.strtok函数(我用来解析命令行参数)

该函数包含在”string.h”头文件中
函数原型:

char* strtok (char* str,constchar* delimiters );

函数功能:
  切割字符串,将str切分成一个个子串
函数参数:
  str:在第一次被调用的时间str是传入需要被切割字符串的首地址;在后面调用的时间传入NULL。
  delimiters:表示切割字符串(字符串中每个字符都会 当作分割符)。
函数返回值:
  当s中的字符查找到末尾时,返回NULL;
  如果查不到delimiter所标示的字符,则返回当前strtok的字符串的指针。
注:strtok函数遇到NULL就会结束

如何实现重定向?

标准输入stdin——-需要处理的数据流
标注输出stdout——–结果数据流
标准错误输出stderr—–错误消息流

默认的三个数据流的文件描述符分别为0,1,2,默认的标准输入为终端键盘IO,标准输出和错误输出都是为终端屏幕。而IO重定向就是将三个数据流定向到别的文件描述符。
最低可用文件描述符

什么是文件描述符? 简单来说就是打开文件的一个索引号。Unix系统中,把打开文件保持在一个数组中,文件描述符即为某文件在此数组中的索引。而最低可用文件描述符的意思就是,每当系统打开一个新文件,则分配一个目前可用的最小的文件描述符用于该文件。每个Unix程序默认打开0,1,2三个文件描述符,其实它们对应的文件就是键盘设备和终端屏幕设备的设备文件。

系统调用dup函数

  int dup(int oldfd);

dup函数的作用是复制oldfd文件描述符给一个最低可用文件描述符。如果我们想将标准输入重定向到新的文件描述符fd,那么我们可以先close(0)关闭标准输入文件描述符,然后调用函数dup(fd),系统则会默认使用最低可用文件描述符指向fd文件描述符对应的文件。最后再关闭fd文件描述符就完成了IO重定向,如下图所示

这里写图片描述

那具体是如何实现的? 关键就在于fork和exec函数之间,exec函数的功能只是利用了磁盘的新程序代替了当前进程的正文段,数据段,堆段和栈段,文件描述符是继承的,除非通过fcntl函数设置了执行时关闭标志.
我们可以在fork函数之后,exec函数之前进行IO重定向.

管道

管道是Unix系统进程通信的一种形式。管道有两个特点:

一般管道的数据只能在一个方向上流动
管道只能在具有公共祖先的两个进程之间使用。通常管道由一个父进程创建,调用fork函数后,这个管道就能在父进程和子进程之间使用了

管道是通过调用pipe函数创建,参数是一个大小为2的int数组,由该参数返回两个文件描述符:fd[0]为读打开,fd[1]为写打开。即fd[1]的输出是fd[0]的输入
这里写图片描述

#include< unistd.h>
#int pipe(int fd[2]);

使用fork函数创建新进程时,也会将父进程的管道复制,就像这样:

这里写图片描述

四.代码

//添加颜色使用命令别名,alias查看
//对于指针数组不能使用strcpy来进行添加 直接衡等就可
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <signal.h>
#include <ctype.h>
#include <string.h>
#include <pwd.h>
#include <wait.h>
#include <readline/history.h>
#include <readline/readline.h>
#define MAXARGS 20
#define ARGLEN 60
int i;
struct parameter
{
    int normal;        //一般命令
    int out_redirect;  //输出重定向  >
    int in_redirect;   //输入重定向   <
    int have_pipe;     //命令中由管道符号
    int backgroud;     //标识命令有没有重定向
    int out_redirects; // >>
};
char oldpwd[300][300];
int o;
struct parameter param;
//cd内置命令
void shell_cd(char *path[]);
void find_command(char *path[]);
void get_ifnput(char *buf)
{
    struct passwd *name; ///头文件在pwd.h之中,需要添加
    name = getpwuid(getuid());
    char pwd[100] = {0};
    getcwd(pwd, sizeof(pwd) - 1); //保存绝对路径
    int len = strlen(pwd);
    char *p = pwd + len;
    char temp[MAXARGS * ARGLEN];
    int pathlen;
    char *isstarm;
    while (*p != '/' && len--) //把当前目录从后往前遍历
    {
        p--;
    }
    p++;
    sprintf(temp, "[%s @myshell %s] :", name->pw_name, p);
    isstarm = readline(temp);
    add_history(isstarm);
    write_history(NULL);
    pathlen = strlen(temp);
    if (pathlen == MAXARGS * ARGLEN)
    {
        perror("too long");
        exit(-1);
    }
    strcpy(buf, isstarm);
    //buf[pathlen]='\n';
    buf[pathlen++] = '\0';
    if(strcmp(buf,"")==0)
    strcpy(buf,"\n");
    free(isstarm);
}
//解析命令行参数
void explain(char *buf, char *list[256])
{
    i = 0;
    char *p = buf;
    while (1)
    {
        if (strcmp(buf, "cd") == 0)
        {
            strcat(buf, " ~");
        }
        if((strcmp(buf,"ll"))==0)
        {
            strcpy(buf,"ls -l --color=auto");
        }
        if (p[0] == '\n')
            break;
        if (p[0] == ' ')
            p++;
        else
        {
            list[i] = strtok(buf, " "); //将他们的空格分开i
           if(strcmp(list[i],"ls")==0)
           {
              list[i+1]="--color=auto";
               i++;
           }
            i++;
            while ((list[i] = strtok(NULL, " ")) != NULL && strcmp(list[i], "\n") != 0)
            { //在这里需要注意strtok的返回值为NULL
                if(strcmp(list[i],"grep")==0)
                {
                    list[i+1]="--color=auto";
                    i++;
                }
                i++;
            }
        }
        if (list[i] == NULL)
        {
            break;
        }
    }
}
void recover_stdio() //dev/tty是终端控制台
{
    int ttyfd;
    ttyfd = open("/dev/tty", O_RDONLY);
    //付给他标准输出和输出和报错
    dup2(ttyfd, 0);
    dup2(ttyfd, 1);
    dup2(ttyfd, 2);
}
//标准的输入输出流分为三种
//>>函数可使用O_APPEND标识符来进行追加操作
void output_redirce(char *filename, int mode)
{

    if (filename == NULL)
        return;
    int fd;
    if (mode == 1)
        fd = open(filename, O_RDWR | O_APPEND | O_CREAT, 0644);
    else
        fd = open(filename, O_WRONLY | O_CREAT, 0644);
    if (fd < 0)
        perror("open error"); 
    dup2(fd, 1);
    close(fd);
    return ;
}
static void intput_redirce(char *filename)
{
    int fd;
    fd = open(filename, O_RDWR);
    if (fd < 0)
        perror("< error");
    dup2(fd, 0);
    close(fd);
    return;
}
//因为将命令传递给execv函数需要去掉
//执行命令
void do_cmd(char *list[])
{
    param.backgroud = 0;
    //如果命令中有&,表示后台运行,父进程直接返回,不等子进程,
    int status;
    int mode = 0;
    pid_t pid;
    int j = 0;
    int flag = 0; //标记有没有特殊字符,比如重定向或者&
    int pid_flag=0;
    //查看有没有后台运行程序
    for (j = 0; j < i; j++)
    {
        if (strncmp(list[j], "&", 1) == 0)
        {
            if (j <= i - 1)
            {
                param.backgroud = 1;
                break;
            }
            if (j > i - 1)
            {
                perror("Wrong command\n");
                return;
            }
        }
        if (strcmp(list[j], "|") == 0)
        {
            flag = 1;
            pid_flag = 1;
            int pipe_fd[2]; //使用pipe函数构建管道
            int pipstatuts;
            pid_t child, child2;
            pipe(pipe_fd);
            char *file[256];
            *file = (char *)malloc(256);//指针数组跟二维数组不同
            memset(file,0,sizeof(file));
            int k=0,n=0,file_flag=0,l;
            for(k=j+1;k<i;k++)
            {
                file[n] = list[k];
                 if(strcmp(file[n],">")==0)
                {
                    file_flag = 1;
                    file[n] = NULL;
                    l=n;
                    n++;
                    continue;
                }
                if(strcmp(file[n],">>")==0)
                {
                    file_flag = 2;
                    file[n]=NULL;
                    l=n;
                    n++;
                    continue;
                }
                if(strcmp(file[n],"<")==0)
                {
                    file_flag = 3;
                    file[n]=NULL;
                    l=n;
                    n++;
                    continue;
                }
                    n++;
            }
            if ((child = fork()) != 0) //函数的父进程
            {
                if ((child2 = fork()) == 0) //子进程
                {

                    close(pipe_fd[1]);    //关闭写端,管道第一个命令需要读入
                    close(fileno(stdin)); //关闭输入
                    dup2(pipe_fd[0], fileno(stdin));
                    close(pipe_fd[0]); //读端结束关闭,防止影响别的*/
                    list[j]=NULL;
                    file[n] = NULL;
                    if(file_flag == 1)
                        output_redirce(file[l+1], mode);
                    if(file_flag ==2)  
                      output_redirce(file[l+1], 1);
                      if(file_flag == 3)
                       intput_redirce(file[l+1]);
                    execvp(file[0],file);
                    exit(0);
                }
                else //在这里child2的父进程迟缓
                {
                    close(pipe_fd[0]);
                    close(pipe_fd[1]);
                    waitpid(child2, &pipstatuts, 0);
                }
                waitpid(child, &pipstatuts, 0);
            }
            else
            {
                close(pipe_fd[0]);     //写数据
                close(fileno(stdout)); //关闭读端
                dup2(pipe_fd[1], fileno(stdout));
                close(pipe_fd[1]);
                list[j] = NULL;
                execvp(list[0], list);
                exit(0);
            }
                param.have_pipe = 3;
                break;
        }
        if (strcmp(list[j], ">") == 0)
        {
            flag = 1;
            pid = fork();
            if (pid == 0)
            {
                param.out_redirect = 1;
                list[j] = NULL;
                output_redirce(list[j + 1], mode);
                find_command(list);
                exit(0);
            }
            break;
        }
        if (strcmp(list[j], "<") == 0)
        {
            flag = 1;
            pid = fork();
            if (pid == 0)
            {
                param.in_redirect = 1;
                list[j] = NULL;
                intput_redirce(list[j + 1]);
                find_command(list);
                exit(0);
            }
            break;
        }
        if (strcmp(list[j], ">>") == 0)
        {
            flag = 1;
            pid = fork();
            if (pid == 0)
            {
                param.out_redirects = 1;
                list[j] = NULL;
                output_redirce(list[j + 1], 1);
                find_command(list);
                exit(0);
            }
            break;
        }
        if (strcmp(list[j], "&") == 0)
        {
            param.backgroud = 1;
        }
    }
    if (flag == 0 && param.backgroud == 0)
    {
        pid = fork();
        if (pid == 0)
        {
            find_command(list);
            exit(0);
        }
    }
    if (param.backgroud == 1) //如果命令中有后台执行的程序
    {
        pid = fork();
        printf("process id %d\n",pid);
        return;
    }
    if(pid_flag == 0)
    {
          if (waitpid(pid, &status, 0) == -1)
        {
          printf("wait for child process error\n");
        }
    }
}
void showhistroy()
{
    read_history(NULL); 
    HIST_ENTRY **history;
    history = history_list();
    int k = 0;
    while (history[k] != NULL)
    {
        printf("%s\n", history[k]->line);
        k++;
    }
}
//查找命令中的可执行命令,在这里应该fork一个子进程让它在后台,不断工作到终止
//不然的话执行一个进程就会死掉
void find_command(char *path[])
{
    int pid;
    int child_info = -1;
    if (*path == NULL)
        return;
    if (strcmp(*path, "exit") == 0 || strcmp(*path, "logout") == 0)
        exit(0);
    if ((pid = fork()) == -1)
        perror("fork");
    else if (pid == 0)
    {
        execvp(path[0], path);
    }
    else
    {
        if (wait(&child_info) == -1)
            perror("wait");
    }
    return;
}
//cd内置命令
void shell_cd(char *path[])
{
    struct passwd *usrname; ///头文件在pwd.h之中,需要添加
    usrname = getpwuid(getuid());
    if (strcmp(path[1], "~") == 0)
    {
        if (strcmp(usrname->pw_name, "kiosk") == 0)
        {
            strcpy(path[1], "/home/kiosk/");
        }
        if (strcmp(usrname->pw_name, "root") == 0)
        {
            strcpy(path[1], "/root/");
        }
    }
    if (strcmp(path[1], "-") == 0)
    {
        strcpy(path[1], oldpwd[o]);
    }
    getcwd(oldpwd[o], sizeof(oldpwd[o]) - 1); //保存绝对路径
    //printf("%s\n",oldpwd[o]);
    o++;
    if (chdir(path[1]) < 0) //chdir可以改变当前的工作目录,fchdir用来将当前目录改为由文件描述符所指定的目录
    {
        printf("%s\n",path[1]);
        perror("cd");
    }
}
int main(int argc, char **argv)
{
    char *buf = NULL;
    buf = (char *)malloc(MAXARGS * ARGLEN);
    char *list[256];
    *list = (char *)malloc(256);
    if (buf == NULL)
    {
        perror("malloc failed");
        exit(-1);
    }
        signal(SIGINT, SIG_IGN);//屏蔽掉信号,ctrl+c
        signal(SIGQUIT, SIG_IGN);
        signal(SIGSTOP, SIG_IGN);//ctrl+z发出信号
        signal(SIGTSTP, SIG_IGN);  
    while (1)
    {
        memset(buf, 0, 1200);
        memset(list, 0, sizeof(list));
        get_ifnput(buf);
        if (strcmp(buf, "exit") == 0 || strcmp(buf, "logout") == 0)
            break;
        if (strcmp(buf,"\n") == 0 )
            continue; //多个回车
        explain(buf, list);
        if (strcmp(list[0], "cd") == 0)
        {
            shell_cd(list);
            continue;
        }
        if (strcmp(list[0], "history") == 0)
        {
            showhistroy();
            continue;
        }
        do_cmd(list);
    }
    free(buf);
    for (i = 0; i < 256; i++) //将二位数组进行释放
        free(list[i]);
    exit(0);
}

猜你喜欢

转载自blog.csdn.net/dream0130__/article/details/81433535
今日推荐