【 압축 파일 】 [OS] Shell

45532 단어 압축 파일
Id: 301521804 name: 우 춘 옥 Homework: shell
작업 링크: 홈 워 크: 셸
이번 작업 은 주로 셸, 시스템 호출, 셸 의 작업 원 리 를 더욱 잘 알 게 하 는 것 이다.6.828 Shell 을 통 해 확장 하면 유 닉 스 API 를 지원 하 는 운영 체제 에서 실행 할 수 있 습 니 다. 예 를 들 어 Linux, MacOS 등 입 니 다.
xv 6 북 의 Chapter 0 을 읽 고 운영 체제 의 인 터 페 이 스 를 알 아 볼 수 있 습 니 다.
첫 번 째 단계: 6.828 셸 의 소스 코드 sh.c 를 다운로드 합 니 다. 코드 는 주로 두 부분 을 포함 합 니 다. 셸 명령 과 실행 명령 을 해석 하고 간단 한 명령 만 고려 합 니 다. 다음 과 같 습 니 다.
ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls |  sort | uniq | wc
rm y

나중에 사용 할 수 있 도록 위의 명령 을 t.sh 파일 에 저장 합 니 다.
주: 컴 파일 sh.c 은 C 컴 파일 러 를 사용 해 야 합 니 다. 설치 가 필요 하지 않 으 면 아래 명령 으로 컴 파일 해 야 합 니 다.
$ gcc sh.c

그리고 같은 폴 더 에서 실행 가능 한 파일 을 생 성 합 니 다 a.out.
현재 디 렉 터 리 구조:
.
├── a.out
├── sh.c
└── t.sh

0 directories, 3 files

방금 컴 파일 된 실행 가능 한 파일 실행:
$ ./a.out < t.sh
redir not implemented
exec not implemented
pipe not implemented
exec not implemented
exec not implemented
pipe not implemented
exec not implemented

실행 한 후에 잘못 보고 할 수 있 기 때문에 우 리 는 이곳 의 일부 기능 을 실현 해 야 한다.
STEP 2: sh.c 에 뭐라고 쓰 여 있 는 지 알 아 보 세 요.
먼저 보기 main() 함수:
int main(void)
{
    //          (   )
    static char buf[100];
    int fd, r;

    // Read and run input commands.
    // while         ,        while    
    while (getcmd(buf, sizeof(buf)) >= 0)
    {
        //    [cd fileName]         ,
        //                 
        if (buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' ')
        {
            // Clumsy but will have to do for now.
            // Chdir has no effect on the parent if run in the child.
            buf[strlen(buf) - 1] = 0; // chop 
if (chdir(buf + 3) < 0) fprintf(stderr, "cannot cd %s
"
, buf + 3); continue; } // fork , // fork() http://www.cnblogs.com/jeakon/archive/2012/05/26/2816828.html // fork() : // fork() : , , // pid // 0 // // if (fork1() == 0) runcmd(parsecmd(buf)); // wait http://www.jb51.net/article/71747.htm // wait() ( wait() ) , // 。 // , // wait() 。 // pid_t wait (int * status); wait(&r); } exit(0); }

터미널 에 입력 이 있 으 면 함수 getcmd() 를 실행 합 니 다.
//     ,             ,        
int getcmd(char *buf, int nbuf)
{
    //          
    if (isatty(fileno(stdin)))
        fprintf(stdout, "6.828$ ");
    //               
    memset(buf, 0, nbuf);
    //                
    if (fgets(buf, nbuf, stdin) == 0)
        return -1; // EOF
    return 0;
}

그 다음 에 while 순환 에 들 어 갑 니 다. 먼저 디 렉 터 리 전환 명령 cd 을 입력 하면 디 렉 터 리 전환 작업 을 직접 수행 하고 그렇지 않 으 면 fork() 키 프로 세 스 를 입력 하여 처리 해 야 합 니 다.
if (fork1() == 0)
  runcmd(parsecmd(buf));
wait(&r);

함수 fork1() 는 하위 프로 세 스 를 fork 하고 부모 / 하위 프로 세 스 의 pid 를 되 돌려 줍 니 다.main() 함수 에서 돌아 오 는 pid 를 판단 하여 현재 실행 중인 프로 세 스 가 어떤 프로 세 스 인지 판단 하고 하위 프로 세 스에 서 해당 하 는 명령 을 수행 합 니 다.부모 프로 세 스 에서 wait(&r) 를 사용 하여 차단 하고 하위 프로 세 스 가 돌아 오 기 를 기다 린 후에 계속 실행 합 니 다.
함수 parsecmd() 는 입력 한 명령 을 해석 하기 위해 서 입 니 다. 너무 많은 관심 을 가지 지 않 아 도 되 지만 함수 parsecmd 의 정의 에서
struct cmd *parsecmd(char *);

구조 체 cmd 의 정의
// All commands have at least a type. Have looked at the type, the code
// typically casts the *cmd to some specific cmd type.
struct cmd
{
    int type; //  ' ' (exec), | (pipe), '' for redirection
};

이 함수 의 기능 은 주로 입력 한 명령 의 종 류 를 판단 하 는 것 임 을 알 수 있다.
그리고 함수 runcmd() 안에 무엇이 있 는 지 보 겠 습 니 다.
void runcmd(struct cmd *cmd)
{
    int p[2], r;
    struct execcmd *ecmd;
    struct pipecmd *pcmd;
    struct redircmd *rcmd;

    if (cmd == 0)
        _exit(0);

    switch (cmd->type)
    {
    default:
        fprintf(stderr, "unknown runcmd
"
); _exit(-1); case ' ': ecmd = (struct execcmd *)cmd; if (ecmd->argv[0] == 0) _exit(0); fprintf(stderr, "exec not implemented
"
); // Your code here ... break; case '>': case ': rcmd = (struct redircmd *)cmd; fprintf(stderr, "redir not implemented
"
); // Your code here ... runcmd(rcmd->cmd); break; case '|': pcmd = (struct pipecmd *)cmd; fprintf(stderr, "pipe not implemented
"
); // Your code here ... break; } _exit(0); }

이 함 수 는 구조 체 cmd 의 매개 변 수 를 받 아들 이 고 이 구조 체 중의 type 값 을 통 해 진일보 한 처 리 를 한다.switch case 문장의 판단 조건 을 통 해 알 수 있 듯 이 명령 의 유형 을 세 가지 로 나 누 었 는데 그것 이 바로 case '' 명령 을 집행 할 수 있 고 case ' case '>' 리 셋 명령 과 case '|' 파이프 명령 이다.우리 가 해 야 할 일 은 서로 다른 유형의 명령 에서 구체 적 으로 명령 을 실행 하 는 코드 를 보완 하 는 것 이다.
세 번 째 단계: 실행 가능 한 명령 실현 (Executing simple commands)
우선 함수 int access(const char *path, int mode); 를 소개 하고 터미널 에서 명령 man access 을 사용 하여 상세 한 소 개 를 볼 수 있 습 니 다.
이 함수 의 기능 은 파일 이나 폴 더 의 접근 권한 을 확인 하 는 것 입 니 다. 예 를 들 어 읽 기, 쓰기 등 입 니 다.
    :
path:         。
mode:    ,    。
      R_OK      //         
      W_OK      //         
      X_OK      //          
      F_OK      //     /       

반환 값 형식 은 int 이 며, 해당 권한 이 있 으 면 0 으로 되 돌아 갑 니 다. 그렇지 않 으 면 함수 가 되 돌아 갑 니 다 -1.
리 눅 스에 서 '모든 것 이 파일' 이기 때문에 우리 가 수행 하 는 리 눅 스 의 명령 도 파일 로 존재 합 니 다.일부 시스템 기본 명령 은 /bin/ 디 렉 터 리 에 존재 합 니 다. ls /bin 디 렉 터 리 에 존재 하 는 명령 도 있 습 니 다. /usr/bin/ 디 렉 터 리 에 다른 명령 은 해당 프로그램 디 렉 터 리 bin 디 렉 터 리 에 존재 하기 때문에 이 명령 을 실행 하기 전에 access() 함 수 를 사용 하여 이 명령 (파일) 이 존재 하 는 지 확인 하 십시오. (F OK)다음 작업 을 진행 하 다.
$ ls /bin 
[          date       expr       ln         pwd        sync
bash       dd         hostname   ls         rm         tcsh
cat        df         kill       mkdir      rmdir      test
chmod      domainname ksh        mv         sh         unlink
cp         echo       launchctl  pax        sleep      wait4path
csh        ed         link       ps         stty       zsh

그 다음 에 우 리 는 함수 int exec(const char *path, char *const argv[]); 를 하나 더 보고 터미널 에서 명령 man 3 exec 을 사용 하여 상세 한 소 개 를 볼 수 있다.
이 함수 의 기능 은 path 경로 의 파일 을 사용 하여 존재 하 는 명령 을 수행 하 는 것 입 니 다.
매개 변수 설명: argv 파일 을 실행 할 경로 입 니 다.path 은 하나의 배열 로 배열 에 실행 할 명령 이 저장 되 어 있 고 배열 의 요소 간 에 빈 칸 으로 연결 되 어 실행 할 명령 이 형성 되 었 다.
반환 값: 실행 에 성공 하면 되 돌아 오지 않 습 니 다. 실행 에 실패 하면 되 돌아 갑 니 다 argv. 실패 원인 은 -1 에 있 습 니 다.
사용 errno 헤더 파일 도입 errno함수 #include 를 사용 하여 오류 코드 에 대응 하 는 설명 정 보 를 얻 을 수 있 습 니 다.
다음은 '명령 집행' 을 시작 하 겠 습 니 다!먼저 생각 을 말 하고 strerror(errno) 함 수 를 사용 하여 실행 할 명령 파일 이 존재 하 는 지 확인 하고 존재 하면 직접 실행 합 니 다. 그렇지 않 으 면 시스템 access() 디 렉 터 리 와 /bin/ 디 렉 터 리 에서 해당 하 는 명령 을 찾 습 니 다. 있 으 면 실행 합 니 다. 그렇지 않 으 면 오 류 를 버 립 니 다.코드 세그먼트 다음 /usr/bin/ 부분의):
    case ' ':
        ecmd = (struct execcmd *)cmd;
        if (ecmd->argv[0] == 0)
            _exit(0);
        // fprintf(stderr, "exec not implemented
");
// Your code here ... if (access(ecmd->argv[0], F_OK) == 0) { execv(ecmd->argv[0], ecmd->argv); } else { const char *bin_path[] = { "/bin/", "/usr/bin/"}; char *abs_path; int bin_count = sizeof(bin_path) / sizeof(bin_path[0]); int found = 0; for (int i = 0; i < bin_count && found == 0; i++) { int pathLen = strlen(bin_path[i]) + strlen(ecmd->argv[0]); abs_path = (char *)malloc((pathLen + 1) * sizeof(char)); strcpy(abs_path, bin_path[i]); strcat(abs_path, ecmd->argv[0]); if (access(abs_path, F_OK) == 0) { execv(abs_path, ecmd->argv); found = 1; } free(abs_path); } if (found == 0) { fprintf(stderr, "%s: Command not found
"
, ecmd->argv[0]); } } break;

수정 이 끝 난 후 컴 파일 하여 실행 할 수 있 습 니 다. 효 과 는 다음 과 같 습 니 다.
$ gcc sh.c 
$  ./a.out 
6.828$ ls
a.out	sh.c	t.sh
6.828$ 
case ' ' 를 사용 하면 프로그램 을 종료 할 수 있 습 니 다.
STEP 4: IO 리 디 렉 션 실현 (I / O redirection)
우선, 구조 체 Ctrl/Command + D 의 정 의 를 살 펴 보 자.
struct redircmd
{
    int type;        // < or >
    struct cmd *cmd; // the command to be run (e.g., an execcmd)
    char *file;      // the input/output file
    int flags;       // flags for open() indicating read or write
    int fd;          // the file descriptor number to use for the file
};

주로 이 구조 체 안의 이러한 속성 을 사용한다.
설명 이 필요 한 것 은 redircmd 파일 설명자 입 니 다.
파일 설명자: 보통 작은 비 마이너스 정수 입 니 다. 커 널 은 특정한 프로 세 스 가 접근 하고 있 는 파일 을 표시 합 니 다.커 널 이 기 존 파일 을 열거 나 새 파일 을 만 들 때 파일 설명 자 를 되 돌려 줍 니 다.파일 을 읽 고 쓸 때 이 파일 설명 자 를 사용 할 수 있 습 니 다.
그 다음 에 생각 을 정리 하고 코드 를 쓰기 시작 할 수 있 습 니 다. 현재 의 표준 입 출력 을 닫 고 지정 한 파일 을 열 어 새로운 표준 입 출력 으로 명령 을 실행 합 니 다.코드 세그먼트 다음 int fd; 부분:
    case '>':
    case ':
        rcmd = (struct redircmd *)cmd;
        // fprintf(stderr, "redir not implemented
");
// Your code here ... close(rcmd->fd); if (open(rcmd->file, rcmd->flags, 0644) < 0) { fprintf(stderr, "Unable to open file: %s
"
, rcmd->file); exit(0); } runcmd(rcmd->cmd); break;

함수 case '' 의 사용 방법 은 파일 IO 상세 설명 (5) - open 함수 상세 설명 참조
수정 이 끝 난 후 컴 파일 하여 실행 할 수 있 습 니 다. 효 과 는 다음 과 같 습 니 다.
$ gcc sh.c 
$ ./a.out 
6.828$ ls > ls.tmp
6.828$ cat < ls.tmp
a.out
ls.tmp
sh.c
t.sh
6.828$ 

위의 두 명령 은 각각 int open(const char * pathname, int flags, mode_t mode); 열 거 된 파일 이름 을 파일 ls 에 저장 하고 ls.tmp 명령 을 사용 하여 파일 cat 의 내용 을 읽 고 표시 하 는 것 이다.
다섯 번 째 단계: 파이프 명령 실현 (Implement pipes) * *
파 이 프 는 두 프로 세 스 (예 를 들 어 fork 에서 나 온 부자 프로 세 스) 간 의 표준 입력 과 표준 출력 을 연결 하 는 메커니즘 으로 여러 프로 세 스 간 의 통신 방법 을 제공 합 니 다. 프로 세 스 가 파 이 프 를 만 들 때 매번 두 개의 파일 설명 자 를 제공 하여 파 이 프 를 만들어 야 합 니 다.그 중 하 나 는 파 이 프 를 쓰 고 다른 하 나 는 파 이 프 를 읽 는 작업 을 한다.파이프 에 대한 읽 기와 쓰 기 는 일반적인 IO 시스템 함수 와 일치 합 니 다. write () 함수 로 데 이 터 를 기록 하고 read () 로 데 이 터 를 읽 습 니 다.
마찬가지 로 구조 체 ls.tmp 의 정 의 를 살 펴 보 자.
struct pipecmd
{
    int type;          // |
    struct cmd *left;  // left side of pipe
    struct cmd *right; // right side of pipe
};

파이프 명령 의 표 지 는 기호 pipecmd 이 고 | 의 왼쪽 과 오른쪽 은 각각 다른 명령 이 므 로 우 리 는 이러한 명령 을 점차적으로 집행 해 야 한다.
그리고 우 리 는 함 수 를 하나 알 아 보 겠 습 니 다. |이 함수 의 역할 은 두 프로 세 스 사이 에 파 이 프 를 만 들 고 두 개의 파일 설명자 int pipe(int fd[2]);fd[0] 를 생 성 하 는 것 입 니 다. 각각 파이프 의 읽 기와 쓰기 단 에 대응 합 니 다. 이 두 프로 세 스 는 이 두 파일 설명 자 를 사용 하여 읽 기와 쓰기 작업 을 할 수 있 습 니 다. 즉, 이 두 프로 세 스 간 의 통신 을 실현 합 니 다.
반환 값: 성공 하면 반환 fd[1], 그렇지 않 으 면 반환 0, 오류 원인 은 -1 에 저 장 됩 니 다.
다른 함수: errno:
이 함수 의 기능 은 기 존 파일 설명 자 를 복사 하 는 것 입 니 다.
반환 값: 성공 하면 같은 파일 을 가리 키 는 새 파일 설명자 (현재 사용 가능 한 파일 설명자 의 최소 값) 를 되 돌려 줍 니 다. 실패 하면 되 돌려 줍 니 다 dup(int old_fd).
코드 세그먼트 -1 부분):
    case '|':
        pcmd = (struct pipecmd *)cmd;
        // fprintf(stderr, "pipe not implemented
");
// Your code here ... // if (pipe(p) < 0) fprintf(stderr, "pipe failed
"
); // fork , // if (fork1() == 0) { // dup close(1); // dup p[1] , dup(p[1]); // close(p[0]); close(p[1]); // left , runcmd(pcmd->left); } // fork , // , // if (fork1() == 0) { // dup close(0); // dup p[0] , dup(p[0]); // close(p[0]); close(p[1]); // right , runcmd(pcmd->right); } close(p[0]); close(p[1]); wait(&r); wait(&r); break;

Linux 명령 case '|' (Word Count): 파일 의 줄 수, 글자 수, 바이트 수 를 통계 하 는 데 사 용 됩 니 다.
예 를 들 어 파일 wc 에는 다음 과 같은 내용 이 포함 되 어 있다.
a.out
sh.c
t.sh
y

명령 사용 y 결 과 는 다음 과 같 습 니 다.
       4       4      18 y

첫 번 째 wc y 는 파일 중 4 줄, 두 번 째 4 는 4 글자 (분할 문자 로 빈 칸 사용), 세 번 째 4 는 18 바이트 (주의, 줄 바 꿈 문자 도 하나의 바이트 로 계산), 네 번 째 18 는 통 계 를 나타 내 는 파일 이름 을 나 타 냈 다.
마지막 으로 컴 파일 하여 실행 할 수 있 습 니 다:
$ gcc sh.c
#    t.sh     
$ ./a.out < t.sh
#     
       4       4      18
       4       4      18

Bingo~
참고 자료:
Homework: shell
6.828 운영 체제 Homework: Shell

좋은 웹페이지 즐겨찾기