Operating System Concepts with java 項目: Shell Unix 和曆史特點

來源:互聯網
上載者:User

標籤:

線程間通訊,fork(),waitpid(),signal,捕捉訊號,用c執行shell命令,共用記憶體,mmap

實驗要求:

1.簡單shell: 通過c實現基本的命令列shell操作,實現兩個函數,main()和setup().

setup讀取使用者的下一條指令(最多80個字元),然後分解為獨立的標記,並執行,使用者按ctrl+D後,程式終止.

    Main函數列印提示符COMMAND->,等待使用者輸入命令,如果使用者命令以” &”結尾,則並發執行,否則,父進程需等待子進程

2.建立曆史特性: 將命令編號,允許使用者訪問最近10個輸入的命令,當使用者按下Ctrl+C時,系統列出這些命令,使用者輸入”r x”可以運行命令,如果x存在,則輸出執行最近的以x為首碼的命令,否則,輸出執行最近的命令.如果該即將執行的命令是錯誤命令,輸出使用者提示,不把這條命令加入曆史緩衝中.

 

一.處理Ctrl+C,Ctrl+D訊號:這裡使用signal()

signal() 轉自百度百科

表標頭檔#include<signal.h> 功 能:設定某一訊號的對應動作 函數原型:void (*signal(int signum,void(* handler)(int)))(int);或者:typedef void (*sig_t)( int );sig_t signal(int signum,sig_t handler); 參數說明:第一個參數signum指明了所要處理的訊號類型,它可以取除了SIGKILL和SIGSTOP外的任何一種訊號。第二個參數handler描述了與訊號關聯的動作,它可以取以下三種值:(1)一個無傳回值的函數地址此函數必須在signal()被調用前申明,handler中為這個函數的名字。當接收到一個類型為signum的訊號時,就執行handler 所指定的函數。這個函數應有如下形式的定義:void func(int sig);(2)SIG_IGN這個符號表示忽略該訊號,執行了相應的signal()調用後,進程會忽略類型為sig的訊號。(3)SIG_DFL這個符號表示恢複系統對訊號的預設處理。 

二:用c執行shell命令,有3種方法:

  1.system(),繼承環境變數,不安全

  2.popen()建立管道,繼承環境變數,不安全

  3.exec族函數+fork()

  這裡使用exec+fork

  exec族函數共有6個,功能是執行對應的檔案,並且輸入附加參數,如果執行成功直接結束,如果不成功會返回-1,接著執行下面的代碼,錯誤資訊儲存在errno中

  本次使用的是execvp,int execvp(const char *file, char *const argv[]);

  其中file傳入shell命令,argv是一個c字串數組,該數組argv[0]是file,arg[1]....arg[n]是分割開的命令參數,arg[n+1]為空白指標NULL,(n是命令參數個數),如"ls -l",則應傳入file="ls",argv[3]={"ls","-l",NULL}

 

exec函數族:轉載自:http://blog.csdn.net/aile770339804/article/details/7443921

  #include <uniSTd.h>

  int execl(cONst char *path, const char *arg, ...);

  int execlp(const char *file, const char *arg, ...);

  int execle(const char *path, const char *arg, ..., char *const envp[]);

  int execv(const char *path, char *const argv[]);

  int execvp(const char *file, char *const argv[]);

  int execve(const char *path, char *const argv[], char *const envp[]);

  exec函數族裝入並運行程式pathname,並將參數arg0(arg1,arg2,argv[],envp[])傳遞給子程式,出錯返回-1。在exec函數族中,尾碼l、v、p、e添加到exec後,所指定的函數將具有某種操作能力有尾碼:


  其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過封裝的庫函數。


  其實我們留心看一下這6個函數,可以發現前3個函數都是以execl開頭的,後3個都是以execv開頭的。

  首先來比較前兩個函數execv和execl。execv開頭的函數是把參數以"char *argv[]"這樣的形式傳遞命令列參數。而execl開頭的函數採用了我們更容易習慣的方式,把參數一個一個列出來,然後以一個NULL表示結束,也可以寫成(char *)0。

  其次緊跟著的2個以p結尾的函數execlp和execvp。與其他幾個函數相比,除execlp和execvp之外的4個函數都要求,它們的第1個參數path必須是一個完整的路徑,如"/bin/ls";而execlp和execvp的第1個參數file可以簡單到僅僅是一個檔案名稱,如"ls",這兩個函數可以自動到環境變數PATH制定的目錄裡去尋找。

  最後兩個函數execle和execve,都使用了char *envp[]來傳遞環境變數。在全部6個函數中,只有execle和execve需要傳遞環境變數,其它的4個函數都沒有這個參數,這並不意味著它們不傳遞環境變數,這4個函數將把預設的環境變數不做任何修改地傳給被執行的應用程式。而execle和execve會用指定的環境變數去替代預設的那些。

  最後要強調一點,大家在平時的編程中,如果用到了exec函數族,一定記得要加錯誤判斷語句。因為與其他系統調用比起來,exec很容易受傷,被執行檔案的位置,許可權等很多因素都能導致該調用的失敗。最常見的錯誤是:

  1. 找不到檔案或路徑,此時errno被設定為ENOENT;

  2. 數組argv和envp忘記用NULL結束,此時errno被設定為EFAULT;

  3. 沒有對要執行檔案的運行許可權,此時errno被設定為EACCES。

 

  因為如果成功了execvp會直接退出,所以execvp應該在由fork產生的子進程中運行

 

 fork入門知識 轉載自http://blog.csdn.net/jason314/article/details/5640969

     一個進程,包括代碼、資料和分配給進程的資源。fork()函數通過系統調用建立一個與原來進程幾乎完全相同的進程,也就是兩個進程可以做完全相同的事,但如果初始參數或者傳入的變數不同,兩個進程也可以做不同的事。
    一個進程調用fork()函數後,系統先給新的進程分配資源,例如儲存資料和代碼的空間。然後把原來的進程的所有值都複製到新的新進程中,只有少數值與原來的進程的值不同。相當於複製了一個自己。

     我們來看一個例子:

[cpp] view plaincopy
  1. /* 
  2.  *  fork_test.c 
  3.  *  version 1 
  4.  *  Created on: 2010-5-29 
  5.  *      Author: wangth 
  6.  */  
  7. #include <unistd.h>  
  8. #include <stdio.h>   
  9. int main ()   
  10. {   
  11.     pid_t fpid; //fpid表示fork函數返回的值  
  12.     int count=0;  
  13.     fpid=fork();   
  14.     if (fpid < 0)   
  15.         printf("error in fork!");   
  16.     else if (fpid == 0) {  
  17.         printf("i am the child process, my process id is %d/n",getpid());   
  18.         printf("我是爹的兒子/n");//對某些人來說中文看著更直白。  
  19.         count++;  
  20.     }  
  21.     else {  
  22.         printf("i am the parent process, my process id is %d/n",getpid());   
  23.         printf("我是孩子他爹/n");  
  24.         count++;  
  25.     }  
  26.     printf("統計結果是: %d/n",count);  
  27.     return 0;  
  28. }  

     運行結果是:
    i am the child process, my process id is 5574
    我是爹的兒子
    統計結果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    統計結果是: 1
    在語句fpid=fork()之前,只有一個進程在執行這段代碼,但在這條語句之後,就變成兩個進程在執行了,這兩個進程的幾乎完全相同,將要執行的下一條語句都是if(fpid<0)……
    為什麼兩個進程的fpid不同呢,這與fork函數的特性有關。fork調用的一個奇妙之處就是它僅僅被調用一次,卻能夠返回兩次,它可能有三種不同的傳回值:
    1)在父進程中,fork返回新建立子進程的進程ID;
    2)在子進程中,fork返回0;
    3)如果出現錯誤,fork返回一個負值;

    在fork函數執行完畢後,如果建立新進程成功,則出現兩個進程,一個是子進程,一個是父進程。在子進程中,fork函數返回0,在父進程中,fork返回新建立子進程的進程ID。我們可以通過fork返回的值來判斷當前進程是子進程還是父進程。

    引用一位網友的話來解釋fpid的值為什麼在父子進程中不同。“其實就相當於鏈表,進程形成了鏈表,父進程的fpid(p 意味point)指向子進程的進程id, 因為子進程沒有子進程,所以其fpid為0.
    fork出錯可能有兩種原因:
    1)當前的進程數已經達到了系統規定的上限,這時errno的值被設定為EAGAIN。
    2)系統記憶體不足,這時errno的值被設定為ENOMEM。
    建立新進程成功後,系統中出現兩個基本完全相同的進程,這兩個進程執行沒有固定的先後順序,哪個進程先執行要看系統的進程調度策略。
    每個進程都有一個獨特(互不相同)的進程標識符(process ID),可以通過getpid()函數獲得,還有一個記錄父進程pid的變數,可以通過getppid()函數獲得變數的值。
    fork執行完畢後,出現兩個進程,

    有人說兩個進程的內容完全一樣啊,怎麼列印的結果不一樣啊,那是因為判斷條件的原因,上面列舉的只是進程的代碼和指令,還有變數啊。
    執行完fork後,進程1的變數為count=0,fpid!=0(父進程)。進程2的變數為count=0,fpid=0(子進程),這兩個進程的變數都是獨立的,存在不同的地址中,不是共用的,這點要注意。可以說,我們就是通過fpid來識別和操作父子進程的。
    還有人可能疑惑為什麼不是從#include處開始複製代碼的,這是因為fork是把進程當前的情況拷貝一份,執行fork時,進程已經執行完了int count=0;fork只拷貝下一個要執行的代碼到新的進程。

 

三.處理序間通訊

  程式需要記錄執行命令是否成功,而exec的結果在fork的子進程內,所以使用mmap匿名映射同一個檔案實現共用記憶體

mmap()及其相關係統調用--轉載自http://fengtong.iteye.com/blog/457090

mmap()系統調用使得進程之間通過映射同一個普通檔案實現共用記憶體。普通檔案被映射到進程地址空間後,進程可以向訪問普通記憶體一樣對檔案進行訪問,不必再調用read(),write()等操作。

註:實際上,mmap()系統調用並不是完全為了用於共用記憶體而設計的。它本身提供了不同於一般對普通檔案的訪問方式,進程可以像讀寫記憶體一樣對普通檔案的操作。而Posix或系統V的共用記憶體IPC則純粹用於共用目的,當然mmap()實現共用記憶體也是其主要應用之一。

1、mmap()系統調用形式如下:

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
參數fd為即將映射到進程空間的檔案描述字,一般由open()返回,同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射(不涉及具體的檔案名稱,避免了檔案的建立及開啟,很顯然只能用於具有親緣關係的處理序間通訊)。len是映射到調用進程地址空間的位元組數,它從被對應檔開頭offset個位元組開始算起。prot 參數指定共用記憶體的存取權限。可取如下幾個值的或:PROT_READ(可讀) , PROT_WRITE (可寫), PROT_EXEC (可執行), PROT_NONE(不可訪問)。flags由以下幾個常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必選其一,而MAP_FIXED則不推薦使用。offset參數一般設為0,表示從檔案頭開始映射。參數addr指定檔案應被映射到進程空間的起始地址,一般被指定一個null 指標,此時選擇起始地址的任務留給核心來完成。函數的傳回值為最後檔案對應到進程空間的地址,進程可直接操作起始地址為該值的有效地址。這裡不再詳細介紹mmap()的參數,讀者可參考mmap()手冊頁獲得進一步的資訊。

2、系統調用mmap()用於共用記憶體的兩種方式:

(1)使用普通檔案提供的記憶體映射:適用於任何進程之間;此時,需要開啟或建立一個檔案,然後再調用mmap();典型調用代碼如下:

fd=open(name, flag, mode);if(fd<0)...


ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通過mmap()實現共用記憶體的通訊方式有許多特點和要注意的地方,我們將在範例中進行具體說明。

(2)使用特殊檔案提供匿名記憶體映射:適用於具有親緣關係的進程之間;由於父子進程特殊的親緣關係,在父進程中先調用mmap(),然後調用fork()。那麼在調用fork()之後,子進程繼承父進程匿名映射後的地址空間,同樣也繼承mmap()返回的地址,這樣,父子進程就可以通過映射地區進行通訊了。注意,這裡不是一般的繼承關係。一般來說,子進程單獨維護從父進程繼承下來的一些變數。而mmap()返回的地址,卻由父子進程共同維護。
對於具有親緣關係的進程實現共用記憶體最好的方式應該是採用匿名記憶體映射的方式。此時,不必指定具體的檔案,只要設定相應的標誌即可,參見範例2。

3、系統調用munmap()

int munmap( void * addr, size_t len )
該調用在進程地址空間中解除一個映射關係,addr是調用mmap()時返回的地址,len是映射區的大小。當映射關係解除後,對原來映射地址的訪問將導致段錯誤發生。

程式碼:

 

#include <signal.h>#include <sys/mman.h>#include <sys/types.h>#include <unistd.h>#include <cstdio>#include <cstring>#include <cstdlib>#include <iostream>#include <sys/wait.h>#include <queue>using namespace std;#define BUFFER_SIZE 50#define MAXLINE 80char buffer[BUFFER_SIZE];struct mes{//用於儲存命令的參數,參數個數,是否並發執行,執行後是否錯誤    char** args;//參數,arg[0]為命令本身    int len;//參數個數    bool bg;//是否並發    int erno;//執行後是否錯誤    mes():args(NULL),len(0),bg(false){}    mes(char **_args,int _len,bool _bg):    args(_args),len(_len),bg(_bg){}    void show(){//列印命令        if(erno==-1)cout<<"ERROR:";        for(int i=0;i<len;i++)cout<<args[i]<<" ";        if(bg)cout<<"&"<<endl;        else cout<<endl;    }    ~mes(){        for(int i=0;i<len&&args[i];i++){            delete[] args[i];            args[i]=NULL;        }        delete[] args;        args=NULL;    }};queue <mes*> rec,quetmp;//rec用於儲存最近10條命令,quetmp只是在遍曆rec時暫時儲存rec中的變數static int quelen=0;//rec內變數個數static int reclen=0;//已執行命令總條數static int srclen=0;//已經讀取字串長度bool read2(char * des,char *src,bool init){//從字串src中讀取非Null 字元串des,失敗返回false, 從同一個src字串讀取應該一次性讀完, init為true時代表重新讀取新的src字串    if(init)srclen=0;    bool fl=false;    for(;src[srclen];srclen++){        if(src[srclen]!=‘ ‘&&src[srclen]!=‘\t‘&&src[srclen]!=‘\n‘){            fl=true;            for(int i=0;src[srclen];srclen++,i++){                if(src[srclen]==‘ ‘||src[srclen]==‘\t‘||src[srclen]==‘\n‘)break;                des[i]=src[srclen];                des[i+1]=0;            }            break;        }    }    return fl;}void setup(char inputBuffer[],char*args[],bool background){//執行命令inputBuffer –args[1] –arg[2]…,background為true代表並發    int len=0;    for(len=0;len<MAXLINE/2+1&&args[len]!=NULL;len++){}//重新建立args數組,以保證資料形式滿足execvp    char **_args=new char*[len+1];    for(int i=0;i<len;i++){        _args[i]=new char[strlen(args[i])+1];        strcpy(_args[i],args[i]);    }    _args[len]=NULL;    mes* pmes=new mes(_args,len,background);    int * p_errno=(int*)mmap(NULL,sizeof(int)*2,PROT_READ|PROT_WRITE,        MAP_SHARED|MAP_ANONYMOUS,-1,0);//共用記憶體以便傳遞子進程的執行結果    int pid=fork();    if(pid==0){        int erno=execvp(inputBuffer,_args);        *p_errno=erno;        _exit(0);//子進程及時退出    }    else{        if(!background){            waitpid(pid,NULL,0);        }    }    pmes->erno=*p_errno;    if(quelen<10){        rec.push(pmes);quelen++;    }    else {        delete rec.front();        rec.pop();        rec.push(pmes);    }    reclen++;}void handle_SIGINT(int sig){//曆史緩衝工具    int ci=reclen-quelen,ind=0;    mes* ls[10];    cout<<endl;    while(!rec.empty()){        cout<<++ci<<": ";        rec.front()->show();        quetmp.push(rec.front());        ls[ind++]=rec.front();        rec.pop();    }
  while(!quetmp.empty()){
    rec.push(quetmp.front());quetmp.pop();
  } cout<<"Exit record input \"q\", repeat command,input \"r\" or \"r x\"(x is the prefix of command)"<<endl; char buff[MAXLINE],buff2[MAXLINE]; int exeNum=ind-1; while(true){ fgets(buff,MAXLINE,stdin); read2(buff2,buff,true); if(strcmp(buff2,"q")==0)break; if(strcmp(buff2,"r")!=0){ cout<<"No such command"<<endl; continue; } if(read2(buff2,buff,false)){ for(;exeNum>=0;exeNum--){ bool fl=true; for(int i=0;buff2[i]!=0;i++){ if(buff2[i]!=ls[exeNum]->args[0][i]){ fl=false; break; } } if(fl)break; } if(exeNum<0)cout<<"No such prefix"<<endl; } if(exeNum>=0){ ls[exeNum]->show(); if(ls[exeNum]->erno==-1){ cout<<"It is an error command!"<<endl; } else { setup(ls[exeNum]->args[0],ls[exeNum]->args,ls[exeNum]->bg); } } } cout<<"record has quitted"<<endl;}void handle_SIGTSTP(int sig){//Ctrl+d退出 write(STDOUT_FILENO,buffer,strlen(buffer)); exit(0);}int main(int argc,char * arg[]){ char inputBuffer[MAXLINE],buff[MAXLINE]; bool background; char * args[MAXLINE/2+1]; memset(args,0,sizeof(args)); signal(SIGINT,handle_SIGINT); signal(SIGTSTP,handle_SIGTSTP); while(true){ background=false; cout<<"COMMAND->"; fgets(inputBuffer,MAXLINE,stdin);//讀取拆分命令 int len=0; while(read2(buff,inputBuffer,len==0)){ if(args[len])delete[] args[len]; args[len++]=new char[strlen(buff)]; strcpy(args[len-1],buff); } if(args[len])delete[] args[len]; if(len>0&&args[len-1][0]==‘&‘){ delete[] args[len-1]; args[len-1]=NULL; len--; background=true; } setup(args[0],args,background); for(int i=0;i<len;i++){ delete args[i]; args[i]=NULL; } } return 0;}

 

Operating System Concepts with java 項目: Shell Unix 和曆史特點

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.