環境變量
環境變量
是指在操作系統中用來指定操作系統運行環境的一些參數
每個人用電腦的習慣不一樣,比如一般把文件放到磁盤,怎么管理文件,用什么編譯器,所以,環境變量就是根據每個人使用操作系統的習慣來規定一些參數
具體特征
- 字符串(本質)
- 有統一的格式:名=值[:值]
- 值用來描述進程環境信息
存儲形式:
與命令行參數類似。char*[]數組,數組名 environ,內部存儲字符串,NULL 作為哨兵結尾。
使用形式:
與命令行參數類似。
加載位置:
與命令行參數類似。位于用戶區,高于 stack 的起始位置。 引入環境變量表:須聲明環境變量。externchar**environ;
打印當前進程的所有環境變量
常見環境變量
按照慣例,環境變量字符串都是 name=value 這樣的形式,大多數 name 由大寫字母加下劃線組成,一般把 name 的部分叫做環境變量,value 的部分則是環境變量的值。
PATH
可執行文件的搜索路徑。ls 命令也是一個程序,執行它不需要提供完整的路徑名/bin/ls,然而通常我們執行當 前目錄下的程序 a.out 卻需要提供完整的路徑名./a.out,這是因為 PATH 環境變量的值里面包含了 ls 命令所在的目錄 /bin,卻不包含 a.out 所在的目錄。PATH 環境變量的值可以包含多個目錄,用:號隔開。在 Shell 中用 echo 命令可以 查看這個環境變量的值:
$echo$PATH
shell解析器按照PATH環境變量中已經設定好的目錄,一個目錄一個目錄找
SHELL
當前 Shell,它的值通常是/bin/bash。
TERM
當前終端類型,在圖形界面終端下它的值通常是 xterm,終端類型決定了一些程序的輸出顯示方式,比如圖形 界面終端可以顯示漢字,而字符終端一般不行。
LANG
語言和 locale,決定了字符編碼以及時間、貨幣等信息的顯示格式。
HOME
當前用戶主目錄的路徑,很多程序需要在主目錄下保存配置文件,使得每個用戶在運行該程序時都有自己的一套配置。
環境變量的相關函數
getenv 函數
獲取環境變量值
chargetenv(constcharname); 成功:返回環境變量的值;失敗:NULL(name 不存在)
setenv 函數
設置環境變量的值
intsetenv(constcharname,constcharvalue,intoverwrite); 成功:0;失敗:-1
參數 overwrite 取值: 1:覆蓋原環境變量 0:不覆蓋。(該參數常用于設置新環境變量,如:ABC=haha-day-night)
unsetenv 函數
刪除環境變量 name 的定義
intunsetenv(constchar*name); 成功:0;失敗:-1
注意事項:name 不存在仍返回 0(成功),當 name 命名為"ABC="時則會出錯。
測試代碼
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(void)
{char *val;const char *name="ABD";//從當前的環境變量表中獲得名字為name的環境變量值,保存到val里val=getenv(name);printf("1, %s = %s\n",name,val);//獲取不出來,出一個空值//覆蓋原有的環境變量setenv(name,"haha-day-and-night",1);//再獲取環境變量val=getenv(name); printf("2, %s = %s\n",name,val);//刪除剛添加的ABDint ret=unsetenv("ABD");//name=value:valueprintf("ret= %d\n",ret); val=getenv(name);printf("3, %s = %s\n",name,val);return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(void)
{char *val;const char *name="ABD";//從當前的環境變量表中獲得名字為name的環境變量值,保存到val里val=getenv(name);printf("1, %s = %s\n",name,val);//獲取不出來,出一個空值//覆蓋原有的環境變量setenv(name,"haha-day-and-night",1);//再獲取環境變量val=getenv(name);printf("2, %s = %s\n",name,val);#if 1int ret=unsetenv("ABDFGH");//name=value:valueprintf("ret= %d\n",ret);val=getenv(name);printf("3, %s = %s\n",name,val);#else//刪除剛添加的ABDint ret=unsetenv("ABD");//name=value:valueprintf("ret= %d\n",ret); val=getenv(name);printf("3, %s = %s\n",name,val);
#endif return 0;
}
進程控制
fork 函數
創建一個子進程。一個進程–>2個進程—>各自對fork做返回
pid_tfork(void); 失敗返回-1;成功返回:
① 父進程返回子進程的 ID(非負整數>0,父進程)
②子進程返回 0 pid_t 類型表示進程 ID,但為了表示-1,它是有符號整型。(0 不是有效進程 ID,init 最小,為 1),返回值=0,是子進程
注意返回值,不是 fork 函數能返回兩個值,而是 fork 后,fork 函數變為兩個,父子需【各自】返回一個
getpid 函數
獲取當前進程 ID
pid_tgetpid(void);
getppid 函數
獲取當前進程的父進程 ID
pid_tgetppid(void);
區分一個函數是“系統函數”還是“庫函數”依據:
- 是否訪問內核數據結構
- 是否訪問外部硬件資源 二者有任一 → 系統函數;二者均無 → 庫函數
getuid 函數
獲取當前進程實際用戶 ID
uid_tgetuid(void);
獲取當前進程有效用戶 ID
uid_tgeteuid(void);
getgid 函數
獲取當前進程使用用戶組 ID
gid_tgetgid(void);
獲取當前進程有效用戶組 ID
gid_tgetegid(void);
創建子進程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{pid_t pid;printf("xxxxxxxxxxxxxx\n");pid=fork();if(pid==-1){ perror("fork error");exit(1);} else if(pid==0){ printf("I am child\n,pid= %u, ppid= %u\n",getpid(),getppid());} else{ printf("I am parent\n,pid= %u, ppid= %u\n",getpid(),getppid());sleep(1);} //這段代碼父子進程都有,所以要打印兩次printf("yyyyyyyyyyyyyyyyyyy\n"); }
循環創建 n 個子進程
一次 fork 函數調用可以創建一個子進程。那么創建 N 個子進程應該是for(i=0;i<n;i++){fork()} 。但這樣創建的并非是N個子進程
當 n 為 3 時候,循環創建了(2^n)-1 個子進程,而不是 N 的子進程。需要在循 環的過程,保證子進程不再執行 fork ,因此當(fork()==0)時,子進程應該立即 break;才正確。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{int i;//循環因子pid_t pid;printf("xxxxxxxxxxxxxx\n");for(i=0;i<5;i++){pid=fork();if(pid==-1){perror("fork error");exit(1);}else if(pid==0){break;// printf("I am %d child\n,pid= %u, ppid= %u\n",i+1 ,getpid(),getppid());}else{// printf("I am parent\n,pid= %u, ppid= %u\n",getpid(),getppid());// sleep(1);}}if(i<5){sleep(i);printf("I am %d child %u\n",i+1,getpid());}else{sleep(i);printf("I am parent\n");}return 0;
}
進程共享
父子進程之間在 fork 后。有一些相同與不同的地方
父子相同處
剛 fork 之后: 父子相同處:
- 全局變量、.
- data、
- text、
- 棧、
- 堆、
- 環境變量、
- 用戶 ID、
- 宿主目錄、
- 進程工作目錄、
- 信號處理方式…
父子不同處:
- 進程 ID
- fork 返回值
- 父進程 ID
- 進程運行時間
- 鬧鐘(定時器)
- 未決信號集
似乎,子進程復制了父進程 0-3G 用戶空間內容,以及父進程的 PCB,但 pid 不同。真的每 fork 一個子進程都要 將父進程的 0-3G 地址空間完全拷貝一份,然后在映射至物理內存嗎?
當然不是!父子進程間遵循讀時共享寫時復制(共享一塊物理地址空間)的原則。這樣設計,無論子進程執行父進程的邏輯還是執行自己 的邏輯都能節省內存開銷。
注意
全局變量各自是獨立的不能共享
重點:
父子進程共享:
- 文件描述符(打開文件的結構體)
- mmap 建立的映射區 (進程間通信詳解)
特別的,fork 之后父進程先執行還是子進程先執行不確定。取決于內核所使用的調度算法。(隨機爭奪)
gdb 調試
使用 gdb 調試的時候,gdb 只能跟蹤一個進程。可以在 fork 函數調用之前,通過指令設置 gdb 調試工具跟蹤父 進程或者是跟蹤子進程。默認跟蹤父進程。
set follow-fork-mode child 命令設置 gdb 在 fork 之后跟蹤子進程。
set follow-fork-mode parent 設置跟蹤父進程。
注意,一定要在 fork 函數調用之前設置才有效。
list 展示代碼
l 顯示剩余代碼
gcc 文件名 -g
gdb a.out
start/run按什么方式往下走(run自動,start逐步)
next/n往下走
quit退出
b 行數 if 條件 //設置條件斷點
info b //查看斷點