Categories
學習筆記

寫屬於自己的shell 并解決zombie問題

上次作業系統概論的老師給了我們一個作業
讓我們寫一個簡單的shell

首先第一個問題:shell是什麼?
wikipedia的解答:

In computing, a shell is a user interface for access to an operating system's services.In general, operating system shells use either a command-line interface (CLI) or graphical user interface (GUI), depending on a computer's role and particular operation.

其實所謂的shell就是一個使用者跟系統服務溝通的橋樑,它可以是命令行界面(CLI)的,也可以是圖形化界面(GUI)的。

而老師要我們寫的shell,基本上就是一個command-line interface的東西,而實作環境是在linux底下。
而shell在接到我們下的command后,到底實際上是怎麼做的呢?

先fork,然後exec。

這時候問題又來了。
什麼是fork?

fork是一個把自己複製一份的過程,複製者叫parent,被複製出來的就是child。

那為什麼要fork?

試想想,如果今天你想要某個東西在後臺執行,而你可以繼續做別的事,是不是需要兩個process同時運行?

接下來,我們就可以開始說怎麼寫shell了。
先做好構思。
(1) 你必須讀取用戶的輸入
(2) 做字串切割,並作出合理的判斷
(3) fork()
(4) exec()
(5) 回到(1)

parent在fork出child,並讓child去exec后,必須等待child完成并回傳值(說自己結束了),child離開,parent才繼續自己的工作,這是原來的設想。
而遇上類似sleep 5 &這種命令的時候,就是用戶想讓程式在後臺執行,則parent必須不做等待,立刻繼續自己的工作,繼續下一次fork,而那個child會因為沒有人搭理(沒有人接收他的回傳值)而變成zombie(也叫defunct),直到程式結束。就如下圖所示:
1

而解決的方法其實也挺簡單,我們先了解一下相關的東西。

When a child process stops or terminates, SIGCHLD is sent to the parent process.

當一個child process停止或結束,SIGCHLD這個signal會被送到parent process。
所以我們可以在程式里一開始就設定SIGCHLD的handler,就可以達到解決zombie的效果。

在linux底下的做法相對簡單,只需要用下面的方法就可以解決。

signal(SIGCHLD, SIG_IGN);

而在mac底下,用上述的方法會導致其他的副作用,比如下面的情況:
2
你會發現第二次ls后,竟然無條件等待上一次的回傳.......那是多麼的尷尬 囧
所以只能自己寫自己的ignore handler

signal(SIGCHLD, ign_handler);
void ign_handler(){
while(waitpid(-1,NULL,WNOHANG)>0);
}

另外你會在上面看到waitpid這個function,這邊也來稍微說明一下wait,以下是我截取的部份:

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

Description
All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state.

The wait() system call suspends execution of the calling process until one of its children terminates. The call wait(&status) is equivalent to: waitpid(-1, &status, 0);

The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument, as described below.

The value of pid can be:
< -1 : meaning wait for any child process whose process group ID is equal to the absolute value of pid.
-1 : meaning wait for any child process.
0 : meaning wait for any child process whose process group ID is equal to that of the calling process.
> 0 : meaning wait for the child whose process ID is equal to the value of pid.

The value of options is an OR of zero or more of the following constants:
WNOHANG: return immediately if no child has exited.
WUNTRACED: also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10): also return if a stopped child has been resumed by delivery of SIGCONT.

基本上所有系統的system call都必須用wait類function去處理,wait()基本上的用於等所有的process,而waitpid()則是等某個pid的process,而如上面我截取的部份你可以看到其實waitpid(-1,NULL,0)和wait(NULL)是一樣的。而後面options的部份是定義waitpid什麼時候return。

大概就寫到這裡吧,有時候再繼續補充,這裡附上我的code,讓各位看官參考:

#include <stdio.h>
#include <stdlib.h> //exit()
#include <string.h>
#include <unistd.h> //exec family
#include <sys/types.h> //wait
#include <sys/wait.h>  //wait
#define MaxNUM 1000
#include <errno.h>
#include <signal.h>

void ign_handler(){
    while(waitpid(-1,NULL,WNOHANG)>0);
}

int main(int argc, const char* argv[]){
	signal(SIGCHLD, ign_handler);	//for mac
	//signal(SIGCHLD, SIG_IGN);		//for linux
	char *str_t;
	str_t = malloc(MaxNUM*sizeof(char));
	char **arg_t;

	char c;			//getchar
	int i;			//index of str_t
	char *p;		//for strtok
	int arg_i;		//index of arg_t
    int count;
    int err;
    int nowait;     //for &
	while(1){
		/* prepare */
		i=0;
        nowait=0;
		strcpy(str_t,"");
		//printf("LKS > "); // my style
        printf(">");
		/* getline */
		c=getchar();
        count=1;
		while(c!='\n'){
			str_t[i]=c;
            if(str_t[i]==' ') count++;
            if(str_t[i]=='&') count--;
			i++;
			c=getchar();
		}
        if(i==0) continue;
		str_t[i]='\0';
		//printf("%s\n",str_t);
        
        /* Counting Arguement & AllocMem Start */
        count++;
        arg_t = malloc(count*sizeof(char*));
        int k=0;
		for(k=0;k<count;k++){
            //printf("Size %d: %lu\n",k,sizeof(arg_t[k]));
			arg_t[k] = malloc(MaxNUM*sizeof(char));

        }
        /* Counting Arguement & AllocMem Finish */
        
		/* Passing Arguement Start */
		arg_i=0;
        p=strtok(str_t," ");
		strcat(p,"\0");
        arg_t[arg_i++]=p;
		p=strtok(NULL," ");
		while(p!=NULL){
            if(strcmp(p,"&")!=0){
                //printf("Old Arguement %d: %s\n",arg_i,arg_t[arg_i]);
                strcat(p,"\0");
                arg_t[arg_i]=p;
                //printf("Arguement %d: %s\n",arg_i,arg_t[arg_i]);
                arg_i++;
                p=strtok(NULL," ");
            }
            else{
                nowait=1;
                p=strtok(NULL," ");
            }
		}
        arg_t[arg_i]=NULL; //the last one of the argv in execvp must be NULL
		/* Passing Arguement Finish */
        
        if(strcmp(arg_t[0],"exit")==0 && arg_t[1]==NULL){ //Build-in Exit
            free(str_t);
			return 0;
		}
        if(arg_t[0]==NULL) continue;
        
		/* Fork */
		pid_t pid;
		pid=fork();
		if(pid<0){ //error occurred
            perror("fork"); //print error in standard lib
			//fprintf(stderr,"Fork failed!\n");
			exit(-1);
		}
		else if(pid==0){ //child process
            err=execvp(arg_t[0],arg_t);
            if(err < 0) {
                perror(arg_t[0]); //print error in standard lib
                //fprintf(stderr,"ERROR : (%s) %s\n",arg_t[0],strerror(errno));
                exit(-1);
            }
		}
		else{ //parent
			if(nowait==0){ //wait(NULL);
				//printf("Waiting for %d!\n",pid);
				waitpid(pid,NULL,0);
			}
            else{
                //printf("No wait!\n");
            }
			//printf("Child Complete!\n");
		}
		free(arg_t);
        arg_t=NULL;
	}
	free(str_t);
	return 0;
}

One reply on “寫屬於自己的shell 并解決zombie問題”

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.