1. 程式人生 > 其它 >作業系統——環境變數&&程式地址空間

作業系統——環境變數&&程式地址空間

技術標籤:linux作業系統

環境變數

**作用:*通過修改環境變數的值,靈活的配置系統執行環境引數,使系統環境配置更加靈活
子程序的繼承性:子程序預設會擁有與父程序相同的環境變數,可以通過環境變數程序程序之間的資料傳遞
命令操作:env set echo export unset
程式碼操作:
程式碼中獲取環境變數:main函式的第三個引數,使用extern char ** environ
char
getenv(char
key)
程式碼中設定環境變數:setenv(char
key, char
val, int override);
putenv(char
key = val)

執行環境引數:就是一些資料,通過這些資料就可以讓我們更加靈活方便的進行系統操作
比如PATH環境變數的資料引數,這個引數中儲存了很多路徑(這些路徑就是默
認搜尋路徑)

程式地址空間

**地址:**記憶體地址—對記憶體以位元組為儲存單元的一個編號,通過地址就能找到具體對應的記憶體單元
**程式:**1.就是一堆死程式碼,儲存在程式檔案中(硬碟);
2.編譯器再編譯程式生成可執行程式檔案時,就會對每一條指令,每一個數據,進行 一個地址排號;
3.當程式執行的時候,就會將指令以及資料放到指定的記憶體地址位置;cpu會根據地址偏移逐步去執行指令,以際找到對應的資料進行處理;
4.程式執行之後才會佔據記憶體因此程式地址空間通常被稱為—程序地址空間

5.一個記憶體地址只能指向一個唯一的記憶體單—一個記憶體單元只能儲存一個數據
在這裡插入圖片描述
程序中所訪問的地址都是虛擬地址,並非實體記憶體地址,
我們所說的程式地址空間實際上也是一個虛擬的地址空間時作業系統通過一個mm_struct結構體所描述的一個假的地址空間
mm_struct(tack_size, start_code, end_code)—通過大小以及區域的編號描述
使用虛擬記憶體的優點:
1.通過虛擬地址空間對映到實體記憶體上進行資料儲存,可以實現資料在實體記憶體上的離散式儲存,提高記憶體的利用率
2.並且每個程序都有自己的虛擬地址空間,因此對於每個程序來說,都會擁有自己的一塊連續的空間使用
3.通過頁表進行對映訪問可以實現更強的記憶體訪問控制
在這裡插入圖片描述

三種記憶體管理方式

分頁式記憶體管理
頁表:頁號,實體地址,缺頁中斷…
虛擬地址的組成:頁號 + 頁內偏移
邏輯對映:拿到虛擬地址,解析出頁號,通過頁號在頁表中找到對應的頁表項,取出頁表項中的實體地址,與頁內拍偏移進行相加
分段式記憶體管理
段表:段號 + 段內偏移
地址的組成:段號 + 實體記憶體段起始地址
邏輯對映:拿到虛擬地址,解析出段號,通過段號在段表中找到對應的段表項,取出段表項中的實體地址,與段內偏移進行相加
段頁式儲存管理
將記憶體分段,在每個段內採用分頁管理
備註:
1.缺頁中斷:通過虛擬地址,找到頁表項之後,如果當前地址本來存放的資料不在記憶體中,就會觸發缺頁中斷
2.為什麼一個數據沒在記憶體中?當實體記憶體不夠用的時候,作業系統會根據一定的演算法,找出一塊實體記憶體,將其中的資料放到磁碟的交換分割槽中進行儲存,騰出這塊記憶體進行使用,當觸發缺頁中斷的時候,在這塊記憶體中原有的資料重新被置換出來

程序控制

建立,終止,等待,程式替換
程序建立
建立一個程序:建立一個pcb,應為pcb在linux下是一個task_struct結構體,這個結構體存放在核心中,只能通過系統呼叫介面實現建立 pid_t fork(void)。通過複製父程序建立一個子程序(複製了父程序pcb中的資料)—程式碼共享資料獨有
在這裡插入圖片描述
**寫時拷貝技術:**子程序創建出來後,與父程序對映訪問同一實體記憶體,當實體記憶體中的資料即將發生改變時,重新定位子程序開闢實體記憶體,拷貝資料過去。(為了避免直接給子程序開闢空間,拷貝資料,而子程序不使用,降低了程序建立效率,造成記憶體冗餘資料)

程序終止
1.main函式中的return
2.在任意位置呼叫 void exit(int status);可以在程式的任意位置退出一個程序,exit退出程序時會重新整理緩衝區,將緩衝區中的資料寫入檔案(庫函式)
3.在任意位置呼叫void _exit(int status);可以在程式的任意位置退出一個程序,_exit退出程序時,直接釋放資源,不會重新整理緩衝區(系統呼叫介面)

程序等待
父程序等待子程序退出,為了獲取退出子程序返回值,釋放退出子程序所有資源避免產生殭屍程序
殭屍程序產生的原因:子程序先於父程序退出,為了儲存退出的返回值,而無法完全釋放資源產生的
int wait(int status);—處理退出的子程序,那麼如果呼叫這個介面的時候沒有子程序退出,則會使父程序阻塞等待,直到有子程序退出
非阻塞:為了完成一個功能,我們發起一個呼叫,但是若當前不具備完成功能的條件,則呼叫立即返回報錯
阻塞:為了完成一個功能,我們發起一個呼叫,但是若當前不具備完成功能的條件,則呼叫等待
ststus:輸出型引數----用於獲取退出子程序的返回值
返回值:成功則返回處理的退出子程序的pid,失敗則返回-1(比如沒有子程序)
int waitpid(int pid, int status, int option);

也是處理退出的子程序,但是與wait不同之處:
1.wait等待的是任意一個子程序的退出(wait是一個父程序假設有很多子程序,任意一個退出,都會處理後呼叫返回)
waitpid可以等待指定的子程序,也可以等待任意一個子程序,通過第一個引數確定(第一個引數pid == -1則表示等待任意)
2.wait是一個阻塞介面(wait如果沒有子程序退出,則會一直等待)
waitpid可以預設阻塞,也可以設定為非阻塞,通過第三個引數確定(第三個引數option == 0表示預設阻塞,option ==WHOHANG則表示非阻塞)
返回值:成功則返回退出子程序的pid大於0
若沒有子程序退出返回0
若出錯則返回-1
注:1.非阻塞操作,通常需要迴圈處理,否則一次處理不成功,總不能不處理了,得迴圈判斷能夠進行處理,直到成功位置
2.通過wait介面獲取子程序退出返回值,返回值只使用了一個位元組進行儲存,並且在返回的時候,並沒有儲存在status的低位 WIFEXITED()/WEXITSTATUS
在這裡插入圖片描述
程式替換
原理:程序程式替換將當前程序的資料段和程式碼段進行替換,替換稱為其它程式的資料段和程式碼段,並且更新堆疊資訊
看到的現象是:1.程序識別符號(PID)是沒有變化的
2.如果替換成功,執行的是替換之後的程式,輸出的內容也和之前的程式沒有半毛錢關係了,而是當前程式的輸出
exec函式簇

#include <unistd.h>
extern char** environ;

在這裡插入圖片描述
l:說明當前函式是可變引數列表的函式
v說明當前函式是以字元指標陣列的方式來進行傳遞引數的
如果函式名中帶有p:則表示會去環境變數PATH來查詢待替換的程式
如果函式名稱中不帶有p:則表示不會搜尋環境變數,需要程式設計師自己指定待替換的程式的路徑
如果函式名稱中帶有e:表示程式設計師需要自己組織環境變數

execve函式是系統呼叫函式
傳遞的引數第一個為代替換的函式的名稱
傳遞的引數的最後一個要以NULL結尾
在這裡插入圖片描述

minishell的實現原理

1.shell是命令列直譯器的統稱
2.當前使用的shell的名子是bash
3.bash其實也是一個程式
我們在命令列中啟動程式,就是命令列直譯器,建立子程序,並且人讓子程序進行程式替換為啟動程序
實現注意: 1.能夠從標準輸入當中接收使用者輸入的命令
 2.建立子程序
 3.子程序進行程式替換
實現程式碼:

makefile

minishell:minishell.c
	gcc $^ -o [email protected] -g

minishell.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <stdlib.h>

char g_Command[1024];

int GetCommand()
{
    //ls -l
   memset(g_Command, '\0', sizeof(g_Command)); 
   printf("[[email protected] ~]");
   fflush(stdout);
   //一定要預留一個\0的位置
   if(fgets(g_Command, sizeof(g_Command) - 1, stdin) == NULL)
   {
       printf("fgets error\n");
       return -1;
   }
   //printf("g_Command : %s\n", g_Command);
   return 0;
}

//argv[]
// argv[0] : 可執行程式的名稱,沒有路徑
// argv[1] : 可執行程式的引數
// ...
// argv[x] : NULL
int ExecCommand(char* argv[])
{
    if(argv[0] == NULL)
    {
        printf("ExecCommand error\n");
        return -1;
    }

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        //child 程序程式替換
        //將已經切割命令好的命令程式(可執行程式以及可執行程式的引數),進行程式替換
        execvp(argv[0], argv); 
        //將沒有替換成功的子程序殺掉
        exit(0);
    }
    else
    {
        //father 程序等待
        waitpid(pid, NULL, 0);
    }
    return 0;
}

int DealCommand(char* command)
{
    if(!command && *command == '\0')
    {
        printf("command error\n");
        return -1;
    }

    //ls -al 區分那一部分是命令,那一部分是命令列引數
    //    ls -al -b -c
    int argc = 0;
    char* argv[1024] = {0};
    //argv[0] = 可執行程式的名稱, 儲存命令的
    //argv[1] = 儲存命令列引數的值
    //...
    //argv[x] = NULL;
    while(*command)
    {
        while(!isspace(*command) && *command != '\0')
        {
            argv[argc] = command;
            argc++;
            //去找下一個空格了
            while(!isspace(*command) && *command != '\0')
            {
                command++;
            }
            *command = '\0';
        }
        command++;
    }
    argv[argc] = NULL;

    //for(int i = 0; i < argc; i++)
    //{
    //    printf("%d:%s\n", i, argv[i]);
    //}

    ExecCommand(argv);
    return 0;
}

int main()
{
    //讀使用者輸入
    while(1)
    {
       if(GetCommand() == -1)
       {
           continue;
       }
       //處理字串
       DealCommand(g_Command);
    }
    return 0;
}