最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux 環(huán)境變量與進(jìn)程地址空間:從命令查找到虛擬地址詳解

 更新時間:2026年07月24日 09:24:47   作者:重生的黑客  
在Linux環(huán)境下,了解和操作環(huán)境變量以及進(jìn)程的地址空間是非常重要的,這里我們將分別討論如何查看環(huán)境變量以及如何理解并操作進(jìn)程的虛擬地址空間,感興趣的朋友跟隨小編一起看看吧

寫在前面

在終端里輸入 ls,系統(tǒng)為什么能直接找到程序;而運(yùn)行自己編譯的文件時,為什么常常要寫 ./code?程序又是怎樣拿到命令行選項(xiàng)、當(dāng)前用戶和工作目錄的?繼續(xù)向下追問,還會遇到一個更反直覺的現(xiàn)象:父子進(jìn)程打印出的變量地址相同,讀到的值卻可以不同。

這些問題看似分散,實(shí)際上由一條主線串在一起:shell 解析命令并維護(hù)運(yùn)行環(huán)境,進(jìn)程繼承這份環(huán)境,而程序中看到的地址屬于每個進(jìn)程自己的虛擬地址空間。

知識框架總覽

一、命令行參數(shù):一個程序怎樣支持多種用法

1.argc與argv是什么

最常見的 main 函數(shù)形式如下:

int main(int argc, char *argv[])

argv 是字符指針數(shù)組,每個元素指向一個參數(shù)字符串;argc 表示有效參數(shù)的數(shù)量。argv[0] 通常保存程序啟動時使用的名稱或路徑,最后還有一個不計(jì)入 argc 的空指針 argv[argc]。

為了先看清數(shù)據(jù),可以寫一個最小程序:

#include <stdio.h>
int main(int argc, char *argv[])
{
    for (int i = 0; i < argc; ++i) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

編譯并運(yùn)行:

gcc -std=c99 -Wall -Wextra argv_demo.c -o argv_demo
./argv_demo -a file.txt

一個重要的簡化邊界是:可以把參數(shù)形成過程直觀理解為“shell 把命令行拆成多個字符串”,但 shell 并非機(jī)械地按空格切割。引號、轉(zhuǎn)義符、變量展開、通配符展開等語法都會參與解析。例如 "hello world" 會作為一個參數(shù)傳入。

2. 為什么命令能帶選項(xiàng)

ls -l、cp -f 等命令本質(zhì)上也是可執(zhí)行程序。程序讀取 argv 后進(jìn)行參數(shù)檢查,再把不同選項(xiàng)分派給不同功能,這便是“同一個二進(jìn)制文件,多種使用方式”的基礎(chǔ)。

下面先實(shí)現(xiàn)一個最小的選項(xiàng)分派程序:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "用法: %s [-a|-b|-c]\n", argv[0]);
        return 1;
    }
    if (strcmp(argv[1], "-a") == 0) {
        puts("功能一");
    } else if (strcmp(argv[1], "-b") == 0) {
        puts("功能二");
    } else if (strcmp(argv[1], "-c") == 0) {
        puts("功能三");
    } else {
        fprintf(stderr, "未知選項(xiàng): %s\n", argv[1]);
        return 1;
    }
    return 0;
}

它還不是成熟的參數(shù)解析器,但已經(jīng)具備三項(xiàng)關(guān)鍵能力:檢查參數(shù)個數(shù)、識別選項(xiàng)、對錯誤用法給出提示。選項(xiàng)較多時,可進(jìn)一步使用 getopt 等接口。

提示: 在 Linux 的典型本地程序中,進(jìn)程首先從運(yùn)行時入口(常見符號為 _start)開始,啟動代碼完成必要初始化后調(diào)用 main。argc、argv 等初始信息來自進(jìn)程啟動約定,并不是運(yùn)行時臨時猜測出來的。

二、PATH:shell 怎樣找到要執(zhí)行的程序

1. 為什么./code要帶路徑,而ls不需要

執(zhí)行程序之前必須先找到程序文件。./code 已經(jīng)明確給出相對路徑,shell 可以直接按該路徑查找;只輸入 ls 時沒有給出路徑,shell 便要查詢 PATH 環(huán)境變量。

PATH 的值由多個目錄組成,目錄之間使用冒號分隔:

echo "$PATH"

shell 依次在這些目錄中尋找名為 ls 的可執(zhí)行文件。找到后執(zhí)行;全部檢查完仍未找到,才報告 command not found。

這也說明自己編譯的程序與系統(tǒng)命令沒有本質(zhì)上的“物種差異”。把可執(zhí)行文件放入 PATH 包含的目錄,或者把它所在目錄加入 PATH,就可以不寫路徑直接運(yùn)行。

2. 修改PATH時不要覆蓋原值

下面的寫法會把原有內(nèi)容整體替換:

PATH=/my/bin

此后 shell 只能在 /my/bin 中搜索,許多外部命令可能突然“消失”。追加目錄時應(yīng)保留舊值:

PATH="$PATH:/my/bin"

這種賦值默認(rèn)只影響當(dāng)前 shell 及其后續(xù)子進(jìn)程。關(guān)閉當(dāng)前 shell 后,內(nèi)存中的修改隨之消失。若要長期生效,需要把導(dǎo)出命令寫入合適的啟動配置文件。

易錯點(diǎn): 不建議為了省略 ./,隨意把未經(jīng)驗(yàn)證的個人程序復(fù)制到 /usr/bin 等系統(tǒng)目錄。把自己的工具目錄加入 PATH 更容易管理,也不會污染系統(tǒng)文件。

三、環(huán)境變量從哪里來,又存在哪里

1. 本質(zhì):一組名稱=值字符串

環(huán)境變量可直觀理解為進(jìn)程運(yùn)行環(huán)境中的參數(shù),例如:

PATH=/usr/local/bin:/usr/bin
HOME=/home/user
SHELL=/bin/bash

在 C 接口中,環(huán)境常被表示為一個以空指針結(jié)束的字符指針數(shù)組。每個指針指向一條以 '\0' 結(jié)束的 名稱=值 字符串。

2. 配置文件負(fù)責(zé)持久化,shell 負(fù)責(zé)構(gòu)建運(yùn)行時環(huán)境

環(huán)境變量不是憑空出現(xiàn)的。登錄或啟動 shell 時,shell 會按照自身類型和系統(tǒng)配置讀取若干文件,執(zhí)行其中的設(shè)置,再形成當(dāng)前進(jìn)程的環(huán)境。

常見文件包括:

  • /etc/profile:常見的系統(tǒng)級登錄 shell 配置入口。
  • ~/.bash_profile:常見的 Bash 用戶級登錄配置。
  • ~/.bashrc:常見的交互式非登錄 Bash 配置。
  • /etc/bashrc/etc/bash.bashrc:名稱和加載方式因發(fā)行版而異。

很多系統(tǒng)會在 ~/.bash_profile 中主動加載 ~/.bashrc,再由 ~/.bashrc 間接加載系統(tǒng)級配置,但這是一種常見組織方式,并非所有系統(tǒng)都完全相同。

運(yùn)行時環(huán)境保存在進(jìn)程上下文中;配置文件則負(fù)責(zé)在下一次啟動 shell 時重建它。因此,臨時執(zhí)行 export 與寫入配置文件解決的是兩個不同時間尺度的問題。

四、常見環(huán)境變量與基本操作

1. 常見變量

  • PATH:可執(zhí)行程序搜索目錄。
  • HOME:當(dāng)前用戶的家目錄,cd ~ 會利用相關(guān)展開規(guī)則回到這里。
  • SHELL:用戶登錄時配置的 shell 路徑;它不一定等于當(dāng)前正在執(zhí)行的 shell。
  • USER:當(dāng)前環(huán)境記錄的用戶名。
  • LOGNAME:登錄名,切換身份時它與 USER 是否變化取決于使用方式和工具行為。
  • HISTSIZE:當(dāng)前 shell 內(nèi)存中保留的歷史命令數(shù)量上限之一。
  • PWD:當(dāng)前工作目錄。
  • OLDPWD:上一次工作目錄,cd - 會利用它完成往返切換。

此外還常見 HOSTNAME、TERM、LANG、SSH_CLIENTSSH_TTY、LD_LIBRARY_PATH 等變量。它們分別服務(wù)于主機(jī)標(biāo)識、終端類型、語言編碼、遠(yuǎn)程會話和動態(tài)庫搜索等場景。

2. 四個基本操作

# 創(chuàng)建并導(dǎo)出環(huán)境變量
export MYENV=1234
# 查看全部環(huán)境變量
env
# 查看一個變量;雙引號可避免不必要的再次分詞
echo "$MYENV"
# 刪除變量
unset MYENV

也可以先創(chuàng)建本地變量,再把它導(dǎo)出:

A=10
export A

五、C 程序獲取環(huán)境變量的三種方式

1. 通過main的第三個參數(shù)

許多 Unix-like 系統(tǒng)支持下面的形式:

#include <stdio.h>
int main(int argc, char *argv[], char *envp[])
{
    (void)argc;
    (void)argv;
    for (int i = 0; envp[i] != NULL; ++i) {
        puts(envp[i]);
    }
    return 0;
}

envp 適合遍歷整個環(huán)境。不過,三個參數(shù)的 main 是廣泛使用的系統(tǒng)擴(kuò)展,不屬于 ISO C 只規(guī)定的兩種標(biāo)準(zhǔn)形式。

2. 使用getenv

只需要一個變量時,getenv 更直接:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const char *path = getenv("PATH");
    if (path == NULL) {
        fputs("PATH 不存在\n", stderr);
        return 1;
    }
    printf("PATH=%s\n", path);
    return 0;
}

getenv 成功時返回變量值字符串的指針,失敗時返回 NULL。調(diào)用方不應(yīng)直接釋放該指針;后續(xù)對環(huán)境的修改還可能使舊指針失效。

3. 使用全局變量environ

#include <stdio.h>
extern char **environ;
int main(void)
{
    for (char **p = environ; *p != NULL; ++p) {
        puts(*p);
    }
    return 0;
}

environ 可以訪問完整環(huán)境表。實(shí)際編程中,需要單個值時優(yōu)先用 getenv;確實(shí)要枚舉全部內(nèi)容時再使用 envpenviron。

六、環(huán)境變量為什么具有繼承性

shell 啟動程序時,會把當(dāng)前環(huán)境傳給新程序。新程序若再創(chuàng)建后代進(jìn)程,這份環(huán)境還可以繼續(xù)向下傳遞,因此環(huán)境變量表現(xiàn)出“全局可見”的效果。

這里的“全局”不是所有進(jìn)程共享同一塊可隨意互改的內(nèi)存。更準(zhǔn)確地說:

  • 環(huán)境沿父進(jìn)程到子進(jìn)程方向傳遞。
  • 子進(jìn)程可以讀取繼承到的值。
  • 子進(jìn)程修改自己的環(huán)境,不會反向修改父進(jìn)程。
  • 不同用戶、不同終端中的 shell 可以擁有不同環(huán)境。

環(huán)境變量適合傳遞啟動參數(shù)和運(yùn)行配置。例如程序可以讀取功能開關(guān)、日志級別或資源路徑。但是環(huán)境變量來自進(jìn)程環(huán)境,通??杀徽{(diào)用者修改,不應(yīng)該獨(dú)自承擔(dān)安全認(rèn)證。

易錯點(diǎn):getenv("USER") 判斷“只有某個用戶可以執(zhí)行”只能作為演示,不能構(gòu)成可靠權(quán)限控制。真實(shí)身份應(yīng)通過 getuidgeteuid 等系統(tǒng)接口判斷,并配合文件權(quán)限或其他安全機(jī)制。

七、本地變量、環(huán)境變量與內(nèi)建命令

1. 本地變量與環(huán)境變量

A=10

此時 A 是當(dāng)前 shell 的本地變量。set 能看到它,但子進(jìn)程默認(rèn)拿不到它。執(zhí)行 export A 后,它才進(jìn)入導(dǎo)出環(huán)境,后續(xù)子進(jìn)程可以繼承。

本地變量的重要用途之一,是支持 shell 自己的腳本語言和交互行為。例如提示符格式由 PS1 等變量控制。

2. 為什么cd和export必須是內(nèi)建命令

一般外部命令會由 shell 找到可執(zhí)行文件,再創(chuàng)建子進(jìn)程執(zhí)行。如果 cd 在子進(jìn)程中改變目錄,退出后父 shell 的工作目錄完全不會變化;如果 export 只修改子進(jìn)程環(huán)境,也無法反向改變父 shell。

因此,cd、export、unset 等需要修改當(dāng)前 shell 狀態(tài)的操作通常由 shell 自己執(zhí)行,稱為內(nèi)建命令(built-in command)。

可以使用下面的命令判斷一個名稱如何被解析:

type cd
type export
type ls

八、從變量地址走進(jìn)進(jìn)程地址空間

把代碼、全局變量、動態(tài)申請的內(nèi)存、局部變量、命令行參數(shù)和環(huán)境變量地址依次打印,會發(fā)現(xiàn)它們大體落在不同區(qū)域。這些區(qū)域共同組成進(jìn)程看到的虛擬地址空間。

從低地址到高地址,常見的簡化布局包括:

  • 代碼段:機(jī)器指令,通常不可寫。
  • 只讀常量區(qū):字符串字面量等只讀數(shù)據(jù)。
  • 已初始化數(shù)據(jù)區(qū):具有非零初值的全局變量、靜態(tài)變量。
  • 未初始化數(shù)據(jù)區(qū)(BSS):未顯式初始化或零初始化的全局變量、靜態(tài)變量。
  • 堆:動態(tài)內(nèi)存分配常用區(qū)域,通常向高地址方向擴(kuò)展。
  • 共享映射區(qū):共享庫、mmap 映射等常見于此。
  • 棧:函數(shù)調(diào)用、局部變量等常用區(qū)域,通常向低地址方向擴(kuò)展。
  • 命令行參數(shù)和環(huán)境變量:進(jìn)程啟動時放入初始棧附近。

這張圖是理解模型,不是要求每一臺機(jī)器都顯示同樣的數(shù)值。體系結(jié)構(gòu)、鏈接方式、內(nèi)核策略、地址空間布局隨機(jī)化(ASLR)以及位置無關(guān)可執(zhí)行文件(PIE)都會影響實(shí)際地址。

靜態(tài)局部變量也能用布局解釋:它的作用域仍受函數(shù)限制,但存儲期貫穿整個程序運(yùn)行過程,因?yàn)樗ǔN挥跀?shù)據(jù)區(qū)或 BSS,而不是隨函數(shù)調(diào)用創(chuàng)建和銷毀的棧幀中。

九、父子進(jìn)程地址相同,值為什么不同

下面的實(shí)驗(yàn)讓父子進(jìn)程都打印全局變量的地址和數(shù)值,只有子進(jìn)程修改變量:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int g_val = 100;
int main(void)
{
    pid_t id = fork();
    if (id < 0) 
    {
        perror("fork");
        return 1;
    }
    for (;;) 
    {
        if (id == 0) 
        {
            ++g_val;
            printf("子進(jìn)程: value=%d, addr=%p, pid=%ld, ppid=%ld\n",
                   g_val, (void *)&g_val,
                   (long)getpid(), (long)getppid());
        } else 
        {
            printf("父進(jìn)程: value=%d, addr=%p, pid=%ld, child=%ld\n",
                   g_val, (void *)&g_val,
                   (long)getpid(), (long)id);
        }
        sleep(1);
    }
}

編譯:

gcc -Wall -Wextra -O0 fork_addr.c -o fork_addr
./fork_addr

典型現(xiàn)象是:父子進(jìn)程打印的 &g_val 數(shù)值相同,父進(jìn)程卻持續(xù)讀到 100,子進(jìn)程則讀到遞增值。

如果打印出的地址是唯一的物理內(nèi)存地址,同一個地址就不該同時對應(yīng)兩個不同值。合理解釋是:

  1. 程序打印的是虛擬地址。
  2. 每個進(jìn)程擁有獨(dú)立的虛擬地址空間。
  3. 相同虛擬地址可以通過各自的頁表映射到不同物理頁。
  4. fork 后父子進(jìn)程最初可以共享只讀物理頁;一方寫入時,內(nèi)核通過寫時拷貝(copy-on-write)為寫入方準(zhǔn)備獨(dú)立物理頁。

因此,“地址數(shù)值相同”不等于“正在使用同一份可寫變量”。虛擬地址到物理地址的轉(zhuǎn)換由操作系統(tǒng)和硬件協(xié)作完成,對普通 C/C++ 指針使用保持透明。

十、邊界與常見誤區(qū)

  • 區(qū):命令行永遠(yuǎn)按空格切分。
  • 空格分隔只是入門模型;引號、轉(zhuǎn)義、展開規(guī)則都會改變最終的 argv
  • 誤區(qū):PATH 負(fù)責(zé)查找所有文件。
  • PATH 主要參與無路徑命令名的可執(zhí)行程序查找,不是通用文件搜索器。
  • 誤區(qū):修改配置文件后當(dāng)前 shell 一定立刻變化。
  • 配置通常在 shell 啟動或被顯式 source 時讀取。修改文件與修改當(dāng)前進(jìn)程環(huán)境是兩件事。
  • 誤區(qū):環(huán)境變量是全系統(tǒng)共享的一張可寫表。
  • 每個進(jìn)程擁有自己的環(huán)境;繼承方向通常是父到子,子進(jìn)程不能靠普通賦值反向修改父進(jìn)程。
  • 誤區(qū):USER 等環(huán)境變量可以作為可信身份。
  • 調(diào)用者可以偽造環(huán)境變量,安全判斷必須使用系統(tǒng)提供的身份與權(quán)限機(jī)制。
  • 誤區(qū):地址空間圖就是物理內(nèi)存排布圖。
  • 它描述的是進(jìn)程虛擬地址空間。不同進(jìn)程可以擁有相同虛擬地址數(shù)值,卻映射到不同物理內(nèi)存。
  • 誤區(qū):每次運(yùn)行的地址都應(yīng)完全相同。
  • ASLR、PIE、體系結(jié)構(gòu)和鏈接方式都會造成地址變化;應(yīng)觀察區(qū)域關(guān)系和行為,而不是死記某個十六進(jìn)制數(shù)值。

總結(jié)

命令行參數(shù)解決了“同一個程序怎樣接受不同指令”的問題,PATH 解決了“shell 去哪里找到程序”的問題,環(huán)境變量則把用戶、目錄、搜索路徑等運(yùn)行信息交給后續(xù)進(jìn)程。繼續(xù)觀察程序中的地址,會發(fā)現(xiàn)代碼、數(shù)據(jù)、堆、棧、參數(shù)和環(huán)境都位于進(jìn)程自己的虛擬地址空間中。fork 實(shí)驗(yàn)進(jìn)一步證明:指針打印出的地址是虛擬地址,相同虛擬地址并不保證指向同一份可寫物理數(shù)據(jù)。

這條主線可以濃縮為一句話:shell 組織程序的啟動環(huán)境,進(jìn)程繼承環(huán)境并運(yùn)行,而操作系統(tǒng)用虛擬地址空間為每個進(jìn)程提供獨(dú)立、連續(xù)且統(tǒng)一的地址視圖。

到此這篇關(guān)于Linux 環(huán)境變量與進(jìn)程地址空間:從命令查找到虛擬地址詳解的文章就介紹到這了,更多相關(guān)linux 環(huán)境變量與進(jìn)程地址空間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Linux ls命令操作詳解

    Linux ls命令操作詳解

    通過 ls 命令,我們可以查看指定目錄下的文件和子目錄,并結(jié)合不同的選項(xiàng)獲取詳細(xì)的文件信息,如權(quán)限、大小、修改時間等,這篇文章主要介紹了Linux ls命令詳解,需要的朋友可以參考下
    2025-04-04
  • Linux下文件剪切的shell腳本實(shí)現(xiàn)代碼

    Linux下文件剪切的shell腳本實(shí)現(xiàn)代碼

    本篇文章主要介紹了Linux下文件剪切的sh腳本實(shí)現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 常用Shell命令集合和使用技巧(推薦)

    常用Shell命令集合和使用技巧(推薦)

    這篇文章主要介紹了最常用Shell命令集合和使用技巧,本文分場景通過實(shí)例講解給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • Linux shell數(shù)組與關(guān)聯(lián)數(shù)組的用法實(shí)例

    Linux shell數(shù)組與關(guān)聯(lián)數(shù)組的用法實(shí)例

    今天小編就為大家分享一篇關(guān)于Linux shell數(shù)組與關(guān)聯(lián)數(shù)組的用法實(shí)例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • linux執(zhí)行sh腳本報錯:$’\r’: 未找到命令的完美解決方法

    linux執(zhí)行sh腳本報錯:$’\r’: 未找到命令的完美解決方法

    本文給大家介紹linux執(zhí)行sh腳本報錯:$’\r’: 未找到命令的完美解決方法,原因分析及解決辦法給大家講解的非常明白,感興趣的朋友跟隨小編一起看看吧
    2023-02-02
  • Linux中多命令執(zhí)行'';''和''&&''的區(qū)別解釋

    Linux中多命令執(zhí)行'';''和''&&''的區(qū)別解釋

    大家有沒有發(fā)現(xiàn)在 Linux 中經(jīng)常使用到一個命令,如 make && make install,這里也可以使用 make ; make install,那么在 Linux 中執(zhí)行命令 ; 和 && 有何區(qū)別?下面通過這篇文章來給大家詳細(xì)的介紹下面,有需要的朋友們可以參考借鑒。
    2016-12-12
  • Linux下自動刪除過期備份和自動異地備份的腳本

    Linux下自動刪除過期備份和自動異地備份的腳本

    這篇文章主要介紹了Linux下自動刪除過期備份和自動異地備份,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • linux文本分析awk基礎(chǔ)命令介紹(8)

    linux文本分析awk基礎(chǔ)命令介紹(8)

    這篇文章主要為大家詳細(xì)介紹了linux文本分析awk基礎(chǔ)命令,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • shell讀取配置文件的方式sed命令詳解

    shell讀取配置文件的方式sed命令詳解

    在編寫啟動腳本時,涉及到讀取配置文件,特地記錄下shell腳本讀取啟動文件的方式,這篇文章主要介紹了shell讀取配置文件-sed命令,需要的朋友可以參考下
    2023-04-04
  • 利用shell腳本進(jìn)行批量處理文件后綴名

    利用shell腳本進(jìn)行批量處理文件后綴名

    這篇文章主要給大家介紹了關(guān)于如何使用shell腳本進(jìn)行批量處理文件后綴名,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2023-12-12

最新評論

凤庆县| 滁州市| 沂南县| 顺义区| 大同市| 上饶市| 澎湖县| 岢岚县| 麦盖提县| 永兴县| 五家渠市| 抚顺市| 启东市| 营口市| 裕民县| 镇平县| 乌苏市| 天津市| 临高县| 旌德县| 许昌县| 乌海市| 海盐县| 辽中县| 扶沟县| 疏勒县| 台东市| 寿阳县| 德惠市| 呼和浩特市| 通化县| 奎屯市| 潍坊市| 双辽市| 湘潭县| 甘洛县| 金阳县| 集安市| 忻州市| 江北区| 湖北省|