第八章 進程和程序:編寫命令解釋器sh

0.摘要

概念與技巧
-Unix shell的功能
-Unix的進程模型
-如何執行一個程序
-如何創建一個進程
-父進程和子進程之間如何通信
相關的系統調用
-fork
-exec
-wait
-exit
相關命令
-sh
-ps

1.什麼是進程

進程就是運行的程序.

2.通過命令ps學習進程

ps會列出當前運行的所有進程
ls會列出當前目錄下的文件信息

ps顯示進程,其中有pid,每一個進程與一個終端相連,每個進程有自己已經運行的時間

ps -a   打印其他終端以及系統的進程
ps -la  打印更加詳細的進程信息
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY      TIME CMD
0 S  1000 18642 18626  0  80   0 - 15028 poll_s pts/1    00:00:00 python
0 S  1000 24240 24199  0  80   0 -  5045 wait   pts/21   00:00:00 man
0 S  1000 24252 24240  0  80   0 -  2848 wait_w pts/21   00:00:00 pager
0 S  1000 25891 23683  0  80   0 -  3775 pause  pts/18   00:00:00 bounce_aio
4 R  1000 25901 19808  0  80   0 -  7663 -      pts/19   00:00:00 ps

S表示當前進程狀態,S(sleep),R(run),uid表示所屬用戶id,PID表示進程id,PPID表示父用戶ID,PRI和NI表示進程優先級和niceness級別(這兩個值和內核中的cpu進程調度有關)。內核通過這兩個值來判斷進程什麼時候執行。niceness就像在排隊的時候讓其他用戶排在自己的前面。SZ表示佔用的內存量,當程序在運行的時候動態的增加內存,會改變進程的大小。 WCHAN列出了睡眠的原因。 ADDR和F已經不再使用,爲了兼容性還是需要。-ly顯示目前使用的進程。

ps -fa  //格式化的顯示信息。

將UID替換成用戶,cmd替換成了執行路徑。在ps中可以看到許許多多的內部程序。

UID        PID  PPID  C STIME TTY          TIME CMD
shengch+  3322  3249  0 105 pts/1   00:01:28 vim -On shm_ts.c shm_t2.
shengch+  4013  3998  0 105 pts/18  00:00:00 man shmget
shengch+  4025  4013  0 105 pts/18  00:00:00 pager
shengch+  6663  6643  0 105 pts/19  00:00:00 man semctl
shengch+  6675  6663  0 105 pts/19  00:00:00 pager
shengch+  8395  8384  0 105 pts/20  00:00:00 man malloc
shengch+  8407  8395  0 105 pts/20  00:00:00 pager
shengch+ 11109 11092  0 09:26 pts/21   00:00:00 ps -fa

當然也可以打印系統進程

ps -ax   打印每個在系統中的進程.

2.1內存和程序

內存可以容納內核和進程的空間.
進程代表內存中的一些字節。進程被拆分成多塊放到內存中。而內核是連續獨立的。
內存中,將程序分割存放在不同的頁面中。將內存表示成連續的字節數組也是一種抽象.
使用操作系統的神奇,並不僅僅是它的文件系統把一堆圓形磁盤上連續的簇變成有序組織的樹狀目錄結構,而且以相似的機制,它的進程系統將硅片上的一些位組織成一個進程社會.

3. shell:進程控制和程序控制的一個工具

shell是一個管理進程和運行程序的程序.shell主要的功能:
1.運行程序:可以運行如,grep/date/ls/echo/mail.這裏這些命令都是c語言寫成,並編譯成機器語言.其實shell有點類似於程序啓動器(program launcher)
2.管理輸入和輸出:重定向功能
3.可編程

3.1shell是如何運行程序的

shell執行一個a.out的三個步驟
1.用戶鍵入a.out
2.shell創建一個進程來運行這個程序
3.程序從磁盤被讀入到內存
4.程序在它結束的時候退出。

3,2shell的主循環

while(!end_of_input)
get command
execute command
wait for command to finish

爲了要寫一個shell:
1.建立一個程序
2.建立一個進程
3.等待exit();

3.3一個程序如何運行另一個程序

使用execvp函數來執行
過程:
1.程序調用execvp
2.內核從磁盤將程序載入
3.內核將arglist複製到進程
4.內核調用main(argc,argv)

main()
{
    char *arglist[3];

    arglist[0] = "ls";
    arglist[1] = "-l";
    arglist[2] = 0;
    printf("***About to exec ls***\n");
    execvp("ls",arglist);
    printf("*** ls is done,bye\n");
}

運行結果

***About to exec ls***
total 104
-rwxrwxr-x 1 shengchen shengchen  8704 1222 19:39 a.out
-rwxrwxr-x 1 shengchen shengchen  8712 16 09:57 exec1
-rw-rw-r-- 1 shengchen shengchen   250 16 09:57 exec1.c
-rwxrwxr-x 1 shengchen shengchen  9136 1221 15:27 psh1
-rw-rw-r-- 1 shengchen shengchen   975 1221 15:27 psh1.c
-rwxrwxr-x 1 shengchen shengchen 12080 1222 20:36 psh2
-rw-rw-r-- 1 shengchen shengchen  1314 1222 20:34 psh2.c
-rwxrwxr-x 1 shengchen shengchen  9032 1221 16:15 waitdemo
-rw-rw-r-- 1 shengchen shengchen   569 1221 16:15 waitdemo1.c
-rwxrwxr-x 1 shengchen shengchen  9096 1222 17:03 waitdemo2
-rw-rw-r-- 1 shengchen shengchen   770 1222 17:02 waitdemo2.c
-rwxrwxr-x 1 shengchen shengchen  8984 1221 16:12 waitdemo.c

:第二個printf打印失效了,說明execvp執行後直接返回,並沒有接下來執行程序中的代碼,調用該函數,內核把對應的命令從文件系統載入,替換了當前執行環境的代碼和數據,因此再調用exec之後呢,就執行命令了.不執行原先的程序.

這裏需要注意的是,內核將程序載入到進程,這個進程就是當前運行這個程序的進程,這樣會覆蓋掉之後的信息。
execvp就像是更換大腦一樣。

接下來就是編寫提示符的shell。一個串一個串的輸入到文件中.

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAXARGLENGTH 100
#define MAXARGNUM 20

void main()
{   
    char argbuf[MAXARGLENGTH];
    char *arglist[MAXARGNUM-1];
    char * makestring(char *);
    int argnum=0;
    int execute(char **);
    while(argnum<MAXARGNUM)
    {
        printf("Args:[%d]?\n",argnum);
        if(fgets(argbuf,MAXARGLENGTH,stdin)&&*argbuf!='\n')
        {
            arglist[argnum++] = makestring(argbuf); //把數據存放到參數列表裏面
        }else{
            if(argnum>0)
            {
                arglist[argnum] = NULL; //close the set 
                execute(arglist);
                argnum =0;
            }
        }
    }
}

/*execute the value of the arglist*/
int execute(char **arglist)
{
    execvp(arglist[1],arglist);
    perror("execute error");
    exit(1);
    return 0;
}
/*malloc the virtual memory for the buf*/
char * makestring(char * buf)
{
    char *cp;
    buf[strlen(buf)-1] = '\0'; //set the end of the buf 
    cp = malloc(strlen(buf)+1);
    if(cp ==NULL)
    {
        perror("malloc error");
        exit(-1);
    }
    strcpy(cp,buf);
    return cp;
}

程序存在執行完成就退出的問題,如何讓它繼續等待下一個命令?

4.如何建立進程來防止退出:fork(一)

fork的執行過程:
1.分配新的內存塊和內核數據結構
2.複製原來的進程到新的進程
3.向運行的進程添加新的進程
4.將控制權返回兩個進程

子進程返回0,父進程返回孩子進程的id.

4.1父進程等待子進程退出。
pid = wait(&status)  //等待某個狀態到來

wait在這裏執行通知和通信的作用,並且返回子進程的pid。用來告訴父進程那個結束了,可以位結束的進程繼續接下來的執行。
父進程處於wait狀態,子進程調用exit()來使父進程退出。當子進程結束,默認調用exit();

/*show how parents pauses until child finishes*/
#include<stdio.h>     
#include<unistd.h>     
#include<stdlib.h>     
#include<sys/wait.h>     
#define DELAY 2     

void child_mode();    
void parent_mode();    
void main()    
{    
    int rf;    
    printf("before fork function\n");    
    rf = fork();    
    if(rf==-1)    
    {    
        perror("fork fail");    
        exit(-1);    
    }else if(rf==0)    
    {    
        child_mode();    
    }else{    
        parent_mode();    
    }    
}    
/*child print process id and the delay*/
void child_mode()    
{    
    printf("this is child %d, and sleep %d\n",getpid(),DELAY);    
    sleep(DELAY);    
}    
/*wait child end, wait return pid of child process*/
void parent_mode()    
{    
    pid_t pid = wait(NULL);    
    printf("this is parent %d, and the child is %d\n ",getpid(),pid);    
}  

子進程與父進程的通信:
wait的目的之一是通知父進程子進程結束,其二是告訴父進程子進程何時結束。
當程序產生錯誤的時候調用exit分配非零的值,這個值和錯誤分配相關程序員可以自己定義。
exit中的status參數,由3個部分組成:8bit記錄退出值,7bit是記錄信號序號(當低7位被kill會產生),另外一個bit致命發生錯誤併產生內核映像(core dump)

#include<stdio.h>     
#include<unistd.h>     
#include<stdlib.h>     
#include<sys/wait.h>     
#define DELAY 5     

void child_mode();    
void parent_mode();    
void main()    
{    
    int rf;    
    printf("before fork function\n");    
    rf = fork();    
    if(rf==-1)    
    {    
        perror("fork fail");    
        exit(-1);    
    }else if(rf==0)    
    {    
        child_mode();    
    }else{    
        parent_mode();    
    }    
}    

void child_mode()    
{    
    printf("this is child %d, and sleep %d\n",getpid(),DELAY);    
    sleep(DELAY);    
    exit(23);    
}    
void parent_mode()    
{    
    int wait_rv;    
    int child_status;    
    int high_8,bit_7,low_7;    
    wait_rv = wait(&child_status);    
    high_8 = child_status>>8;    /*1111 1111 0000 0000*/
    low_7 = child_status & 0x7f; /*0000 0000 0111 1111*/   
    bit_7 = child_status &0x80;   /*0000 0000 1000 0000*/
    printf("status of child is high8 %d, low_7 is %d, bit_7 is %d",high_8,low_7,bit_7);    
}    

當測試的時候,可以在程序運行時,kill當前運行程序的pid,之後會在父進程中返回status的低7位的值.
具體流程

具體流程

5.實現一個shell : psh2.c

/*prompting shell version 2*/

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAXARGS 20  /*cmdline args*/
#define ARGLEN 100  /*token length*/

int main()
{   
    int execute(char **);
    char * arglist[MAXARGS];
    char buf[ARGLEN];
    int numargs = 0;
    char* makebuf(char *); 
    while(numargs < MAXARGS)
    {   
        printf("arg number is %d\n",numargs);
        if(fgets(buf,ARGLEN,stdin)&& *buf!='\n')
        {   
            printf("buf is %s",buf);
            arglist[numargs++] = makebuf(buf);
        }else{
            if(numargs)//try the numargs>0
            {   
                arglist[numargs] =NULL;
                execute(arglist); 
                numargs =0; 
            }   

        }   
    }   
    return 0;
}
int execute(char **arglist)
{
    int pid,status;
    pid = fork();
    switch(pid)
    {   
        case -1: 
            perror(" fork failed");
            break;
        case 0:
            execvp(arglist[0],arglist);
            perror("execute failed");
            exit(1);
            break;
        default:
            signal(SIGINT,SIG_IGN);
            while(wait(&status)!=pid);
            printf("child exited with status %d, %d\n",status>>8,status &0377);
    }
}
/*copy string from stack to heap*/
char * makebuf(char *buf)
{
    buf[strlen(buf)-1] = '\0';  //替換最後的結尾的'\n'
    char * cp = malloc(strlen(buf)+1);
    if(cp==NULL)
    {
        perror("no memory\n");
        exit(1);
    }
    strcpy(cp,buf);
    return cp;
}

鍵盤發送給所有的ctrl+c的SIGINT信號給所有連接的進程。如何讓自己編寫的shell不被殺死,只殺死運行的程序

6.思考:用進程編程

1.execvp/exit就像call/return
(1)call/return:調用函數,傳入參數,執行操作,返回值
(2)exec/exit:通過fork/exec來執行傳入參數,完成之後exit退出,exit中的參數n被傳出,到達wait(),得到對應的status
2.全局變量和fork/exec
fork/exit, exit/wait

7.exit和exec的其他細節

進程死亡:exit和_exit
exit退出調用由atexit和on_exit註冊的函數,執行當前系統定義的其他與exit相關的操作。然後調用_exit。

已經死亡但是沒有給exit賦值的進程稱爲幽靈(zombie)進程。ps列出並標記位defunct.
_exit()小結如下。
1.關閉所有文件描述符和目錄描述符
2.將該進程的PID置爲init進程的PID.
3.如果父進程調用wait或waitpid來等待子進程結束,則通知父進程
4.向父進程發送SIGCHLD

如果父進程在子進程之前退出,那麼子進程將繼續運行,而不會稱爲“孤兒”,它們將是init進程的”子女”,有了國家監管的感覺。注意,即使父進程沒有調用wait,內核也會向它發送SIGCHLD消息。

exec家族

execvp是調用execve,它本身只是一個庫函數,真正的系統調用是execve.具體參看手冊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章