進程(三)----(進程創建、進程終止、進程等待、進程替換)字符串解析,實現一個minishell

進程控制

1、進程創建

pid_t fork(void);- - - - -創建一個進程(父子進程數據獨有,代碼共享)

在這裏插入圖片描述
寫時拷貝技術(提高進程創建效率):子進程複製了父進程,一開始與父進程指向同一塊物理內存;因此看起來父子進程完全相同;但是進程之間具有獨立性,意味着當這塊物理內存中數據即將發生改變時會重新給子進程開闢物理內存,將數據拷貝過來,因爲子進程應該有自己的數據。按理說每個進程都應該有自己獨立的內存空間,但若創建子進程時直接開闢空間拷貝技術,會比較緩慢,並且拷貝過來的數據子進程從來都不用。
代碼共享"因爲代碼段是隻讀的。
**子進程創建pcb的時候,也會創建頁表,虛擬地址空間,然後從父進程pcb中拷貝這些數據過來。

  • vfork創建的子進程,父子進程可以同時運行
  • pid_t vfork(void):創建一個子進程,並且阻塞父進程,直到子進程exit退出或者程序替換之後,父進程纔會進行
  • vfork創建子進程效率比較高,因爲vfork創建子進程後父子進程會共用同一個虛擬空間。
  • 在這裏插入圖片描述
  • 早期使用vfork是因爲它創建子進程效率高,但是fork實現了寫時拷貝之後,創建效率也變高了,並且使用起來更加靈活,因此vfork現在已經很少使用了。

創建子進程優點:讓子進程調度另一個程序運行。(有一個任務,父進程可以幹,但是怕有風險,萬一數據處理出錯崩潰了,因此創建一個子進程,讓子進程去幹)

進程創建的流程:在內核中調用clone接口創建pcb,從父進程pcb拷貝數據進來,寫時拷貝技術。

進程終止(退出一個進程)

  • main函數return 退出進程時會退出緩衝區
  • void exit(int status);- - - - - 庫函數- - - - - 退出調用進程,將status作爲返回值返回給父進程
  • void _exit(int status);- - - - - 系統調用接口- - - - -退出調用進程,將status返回給父進程

庫函數和函數調用接口的關係?(庫函數封裝了系統調用接口,系統調用接口不太好用)

  • exit/return退出時都會刷新緩衝區,_exit退出時直接釋放資源,不刷新緩衝區
  • return只會在main函數中的時候纔會退出進程,而exit是在任意位置調用都會推出調用進程

以上幾種進程退出方式都屬於進程的正常退出,但是會根據返回值向父進程表示爲什麼退出。
在進程退出時也可能異常退出,(如:程序中出現分母爲零情況)- - - - -程序崩潰- - - - -程序並沒有運行完畢,突然因爲某種錯誤退出了

進程等待(父進程等待子進程的退出,獲取子進程的返回值,避免產生殭屍進程)

pid_t wait(int *status);- - - - -阻塞等待任意一個子進程退出,獲取子進程的返回值到status指向的空間中,並且釋放子進程資源,返回退出的子進程pid。
阻塞:爲了完成某個功能發起一個調用,若目前不具備完成功能的條件,則調用不返回一直等待;
非阻塞:爲了完成某個功能發起一個調用,若當前不具備完成功能的條件,則立即報錯返回;非阻塞操作通常需要循環操作。

*pid_t waitpid(pid_t pid,int status,int options);
pid:若== -1,則表示等待任意一個子進程退出;若 > 0,則表示等待指定的子進程退出。
status:輸出型參數,傳入一個 int 空間的首地址,獲取退出的子進程返回值。
options:選項參數,0- - - 表示默認阻塞等待;WNOHANG- - -表示將waitpid設置爲非阻塞,沒有子進程已經退出的話就立即報錯返回,返回值爲0。
waitpid返回值:若等待到了子進程退出則返回子進程的pid;若有子進程,但是沒有退出則返回0;出錯返回 -1.

  • 非阻塞操作相較於阻塞操作,對CPU利用更加充分,因爲不在一直等待,但必須循環進行操作
  • wait和waitpid並不是只處理剛退出的程序,而是對已經退出的進程進行處理(不管什麼時候退出的)

int status == status這個變量有四個字節的空間,向函數傳入地址
int *status 定義了一個指針變量,有8個字節空間,存放的是另一塊空間的地址
返回值的獲取:子進程退出的返回值只使用了一個字節保存,並且通wait/waitpid函數的status返回給用戶;然而這個status有4個字節,返回值其實保存在低16位的高8位(即:(status>>8)&0xff)
在這裏插入圖片描述
在這裏插入圖片描述
首先獲取低7位,異常退出信號值,通過是否爲0判斷進程是否正常退出
00000000 00000000 00000000 01111111 & status = 低7位 0x7f &status
WIFEXITED(status)- - - - - 用於根據status判斷進程是否正常退出
WEXITSTATUS(status)- - - - - 從status中取出子進程退出返回值

因爲得到進程退出可能是正常退出,也可能是異常退出的。獲取一個返回值的前提是這個進程是正常退出的!

判斷子進程是否正常退出:

在這裏插入圖片描述
信號:通知進程發生了某個事件,中斷進程當前的操作,去處理這個事件。而操作系統中的信號的體現是一個數字,某個數字表示某個異常事件。程序因爲異常退出的時候,則會將這個異常退出事件的信號放置到低7位中。

程序替換

創建子進程的目的:1、讓子進程幹着和父進程一樣的事情,分攤壓力; 2、讓子進程幹其他事情,讓子進程背鍋
如何讓子進程幹其他事情?

1、通過fork的返回值進行分流後,子進程的 if(fork()==0)- - - - -子進程乾的事情
這種方式存在的缺陷:

  • 程序的耦合度很強,因爲所有的功能代碼都是在當前程序中編寫的,如果想要改變子進程的功能處理流程,需要修改整個代碼,然後重新進行編譯;導致程序變得非常龐大。

程序替換:替換一個正在調度的程序信息(進程是pcb,負責調度管理程序的運行,運行的數據和代碼都在內存中),將另一個程序加載到內存中,然後讓原有的pcb不再調度原來的程序,而去調度這個新的程序。
在這裏插入圖片描述

如何在代碼中完成程序替換,讓一個進程調度運行另一個程序

在這裏插入圖片描述
l和v的區別

在這裏插入圖片描述
有沒有e的區別
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
一個進程的環境變量是它的父進程賦予的
我麼在終端運行一個程序,這個程序的父進程實際上是shell程序。當我們在shell中輸入一個命令,這時候其實shell對這個標準輸入進行解析,得到了程序名稱;然後創建子進程,並且將子進程的程序替換爲當前這個解析出來的程序。

實現一個minishell

  1. 等待標準輸入- - - - - fgets();//從標準輸入一行數據
  2. 獲取到標準輸入後,進行字符串解析,得到命名名稱以及運行參數;
  3. 創建子進程 - - - - - fork
  4. 子進程中進行程序替換,運行指定的命令- - - - - execvp程序替換(不需要指定程序路徑)
  5. 父進程等待子進程命令執行完畢- - - - - wait(避免殭屍進程)

字符串解析
在這裏插入圖片描述

//將[  ls  -l  -a  ]解析爲 [ls] [-l] [-a]
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char buf[]={  ls  -l  -a  };
char* ptr = buf;
char* n_argv[32] = {NULL};
int n_argc = 0;
while(*ptr != '\0')
{
	if(*ptr != ' ')  //當前字符不爲空格
	 {
	 	n_argv[n_argc] = ptr;   //argv[0]=[ls  -l  -a  ],argv[1] =[s  -l  -a  ]
	 	n_argc++;
	 	while(*ptr != ' ')       //讓ptr把ls走完,走到下一個空格處
	 		ptr++;
	 	*ptr = '\0';  //把s字符後的第一個空格置位字符串結束標準\0,這樣argv[0]=[ls]起始是l的位置,結束是s後的\0位置
	 }
	 ptr++;
}
}

實現一個minishell

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    while(1) {
        //增加一個shell提示
        printf("[san@minishell]$ ");
        fflush(stdout);//刷新標準輸出緩衝區
        //1. 等待標準輸入
        char buf[1024] = {0};
        fgets(buf, 1023, stdin);
        buf[strlen(buf)-1] = '\0'; //buf[...]='\n'
        //2. 對輸入數據進行解析
        char *argv[32] = {NULL};
        int argc = 0;
        char *ptr = buf;
        // [    ls    -a    -l    ]
        while(*ptr != '\0') {
            if (*ptr != ' ') {
                argv[argc] = ptr;
                argc++;
                while(*ptr != ' ' && *ptr != '\0') {
                    ptr++;               
                }
                *ptr = '\0';
            }
            ptr++;
        }
        argv[argc] = NULL;//最後一個參數的下一個位置置NULL
        //3. 創建子進程 4. 在子進程中程序替換
        pid_t pid = fork();
        if (pid == 0) {
            //execvp(char *file, char *argv[])  file--新程序名稱 
            execvp(argv[0], (char**)argv);//程序替換成功就去運行新程序了,32行以後就不會運行了
            //能夠走到第33行,那麼肯定程序替換失敗了
            perror("execvp error");//打印上一次系統調用接口使用的錯誤原因
            exit(0);
        }
        //5. 進程等待
        wait(NULL);
    }
    return 0;
}

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