一、環(huán)境變量
1.什么是環(huán)境變量
首先,在百度百科中,環(huán)境變量的解釋是這樣的:
環(huán)境變量(environment variables)一般是指在操作系統(tǒng)中用來(lái)指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù),如:臨時(shí)文件夾位置和系統(tǒng)文件夾位置等。環(huán)境變量是在操作系統(tǒng)中一個(gè)具有特定名字的對(duì)象,它包含了一個(gè)或者多個(gè)應(yīng)用程序所將使用到的信息。例如Windows和DOS操作系統(tǒng)中的path環(huán)境變量,當(dāng)要求系統(tǒng)運(yùn)行一個(gè)程序而沒有告訴它程序所在的完整路徑時(shí),系統(tǒng)除了在當(dāng)前目錄下面尋找此程序外,還應(yīng)到path中指定的路徑去找。用戶通過(guò)設(shè)置環(huán)境變量,來(lái)更好的運(yùn)行進(jìn)程。
指令本質(zhì)上就是編譯好的程序和腳本,被存儲(chǔ)在特定的路徑下(默認(rèn)/user/bin/)。
比如我們執(zhí)行 ls 指令,實(shí)際上就是執(zhí)行這個(gè)程序或者腳本,而我們要執(zhí)行一個(gè)程序就必須找到該程序,我們通常要運(yùn)行一個(gè)可執(zhí)行程序,是用 ./a.out
來(lái)執(zhí)行的,./
表示在當(dāng)前目錄,a.out
表示一個(gè)可執(zhí)行程序。所以我們要執(zhí)行 ls 指令,就要找到 ls 所在的路徑,而環(huán)境變量的作用就是讓系統(tǒng)從指定的路徑去找,而在PATH 中有/uer/bin
路徑,所以我們就能執(zhí)行所有的指令了。
常用的10個(gè)環(huán)境變量如下:
環(huán)境變量名稱 | 作用 |
---|---|
HOME | 用戶的主目錄(也稱家目錄) |
SHELL | 用戶使用的 Shell 解釋器名稱 |
PATH | 定義命令行解釋器搜索用戶執(zhí)行命令的路徑 |
EDITOR | 用戶默認(rèn)的文本解釋器 |
RANDOM | 生成一個(gè)隨機(jī)數(shù)字 |
LANG | 系統(tǒng)語(yǔ)言、語(yǔ)系名稱 |
HISTSIZE | 輸出的歷史命令記錄條數(shù) |
HISTFILESIZE | 保存的歷史命令記錄條數(shù) |
PS1 | Bash解釋器的提示符 |
郵件保存路徑 |
Linux 作為一個(gè)多用戶多任務(wù)的操作系統(tǒng),能夠?yàn)槊總€(gè)用戶提供獨(dú)立的、合適的工作運(yùn)行環(huán)境,因此,一個(gè)相同的環(huán)境變量會(huì)因?yàn)橛脩羯矸莸牟煌哂胁煌闹怠?/p>
2.環(huán)境變量的分類
按照變量的生存周期劃分,Linux 變量可分為兩類:
- 永久的:需要修改配置文件,變量永久生效。
- 臨時(shí)的:使用 export 命令聲明即可,變量在關(guān)閉 shell 時(shí)失效。
按作用的范圍分,在 Linux 中的變量,可以分為環(huán)境變量和本地變量:
- 環(huán)境變量:相當(dāng)于全局變量,存在于所有的 Shell 中,具有繼承性;
- 本地變量:相當(dāng)于局部變量只存在當(dāng)前 Shell 中,本地變量包含環(huán)境變量,非環(huán)境變量不具有繼承性。
3.查看環(huán)境變量
值得一提的是,Linux 系統(tǒng)中環(huán)境變量的名稱一般都是大寫的,這是一種約定俗成的規(guī)范。
使用 echo 命令查看單個(gè)環(huán)境變量,例如:echo $PATH
;使用 env 查看當(dāng)前系統(tǒng)定義的所有環(huán)境變量;使用 set 查看所有本地定義的環(huán)境變量。查看 PATH 環(huán)境的實(shí)例如下:
常用的命令如下:
- echo: 顯示某個(gè)環(huán)境變量值
- export: 設(shè)置一個(gè)新的環(huán)境變量
- env: 顯示所有環(huán)境變量
- unset: 清除環(huán)境變量
- set: 顯示本地定義的shell變量和環(huán)境變量
例如我們要設(shè)置一個(gè)新的環(huán)境變量,然后再清除:
`
4.設(shè)置環(huán)境變量
在 Linux 中設(shè)置環(huán)境變量有三種方法:
1.所有用戶永久添加環(huán)境變量: vi /etc/profile
,在 /etc/profile
文件中添加變量。
2.當(dāng)前用戶永久添加環(huán)境變量: vi ~/.bashrc
,在用戶目錄下的 ~/.bashrc
文件中添加變量。
3.臨時(shí)添加環(huán)境變量 : 可通過(guò) export 命令,如運(yùn)行命令export HELLO=100
。
5.獲取環(huán)境變量
- 命令行第三個(gè)參數(shù)
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}
- 通過(guò)第三方變量environ 獲取
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}
lib.c中定義的全局變量environ指向環(huán)境變量表,environ沒有包含在任何頭文件中,所以在使用時(shí)要用extern聲明。
- 通過(guò)系統(tǒng)調(diào)用獲取
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
二、進(jìn)程控制
1.進(jìn)程終止
正常退出:
- 從 main 函數(shù)返回
- 調(diào)用exit
- 調(diào)用_exit
異常退出:
- crtl + c,信號(hào)終止
_exit():
#include <unistd.h>
void _exit(int status);
DESCRIPTION:
The function _exit() terminates the calling process “immediately”. Any open file descriptors belonging to the process are closed; any children of the process are inherited byprocess 1, init, and the process’s parent is sent a SIGCHLD signal.
_eixt()函數(shù)立即終止進(jìn)程,關(guān)閉所有屬于該進(jìn)程的文件描述符,其子進(jìn)程被1號(hào)init 進(jìn)程領(lǐng)養(yǎng),然后向父進(jìn)程發(fā)送SIGCHLD 信號(hào)。
The value status is returned to the parent process as the process’s exit status, and can be collected using one of the wait(2) family of calls.
值狀態(tài)作為進(jìn)程的退出狀態(tài)返回到父進(jìn)程,并且可以使用 wait(2) 系列調(diào)用之一來(lái)收集。
exit():
#include <unistd.h>
void exit(int status);
- 執(zhí)行用戶通過(guò) atexit或on_exit定義的清理函數(shù)。
- 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
- 調(diào)用_exit
return是一種更常見的退出進(jìn)程方法。執(zhí)行return n等同于執(zhí)行exit(n),因?yàn)檎{(diào)用main的運(yùn)行時(shí)函數(shù)會(huì)將main的返回值當(dāng)做 exit的參數(shù)。
2.進(jìn)程等待
進(jìn)程等待的必要性:
- 子進(jìn)程退出,父進(jìn)程如果不管不顧,就可能造成‘僵尸進(jìn)程’的問題,進(jìn)而造成內(nèi)存泄漏。
- 進(jìn)程一旦變成僵尸狀態(tài),那就刀槍不入,“殺人不眨眼”的kill -9 也無(wú)能為力,因?yàn)檎l(shuí)也沒有辦法殺死一個(gè)已經(jīng)死去的進(jìn)程。
- 父進(jìn)程派給子進(jìn)程的任務(wù)完成的如何,我們需要知道。如,子進(jìn)程運(yùn)行完成,結(jié)果對(duì)還是不對(duì),或者是否正常退出。
- 父進(jìn)程通過(guò)進(jìn)程等待的方式,回收子進(jìn)程資源,獲取子進(jìn)程退出信息
wait():用來(lái)等待任何一個(gè)子進(jìn)程退出,由父進(jìn)程調(diào)用。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待進(jìn)程pid,失敗返回-1。
參數(shù):
輸出型參數(shù),獲取子進(jìn)程退出狀態(tài),不關(guān)心則可以設(shè)置成為NULL
wait方式:
阻塞式等待,等待的子進(jìn)程不退出時(shí),父進(jìn)程一直不退出;
waitpid():
pid_ t waitpid(pid_t pid, int *status, int options);
參數(shù):
pid:
Pid=-1,等待任一個(gè)子進(jìn)程。與wait等效。
Pid>0.等待其進(jìn)程ID與pid相等的子進(jìn)程。
status:
WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的退出碼)
options:
當(dāng)options參數(shù)為0時(shí),與wait功能相同,仍是阻塞式等待,不提供額外功能
如果為下列常量按位或則提供更多功能:
WCONTINUED:若實(shí)現(xiàn)支持作業(yè)控制,那么由pid指定的任一子進(jìn)程在暫停后已經(jīng)繼續(xù),但狀態(tài)尚未報(bào)告,則返回狀態(tài)
WNOHANG:若由pid指定的子進(jìn)程并不是立即結(jié)束,則waitpid不阻塞,即此時(shí)以非阻塞方式(輪詢式訪問的必要條件)等待子進(jìn)程,并且返回0。若正常結(jié)束,則返回該?進(jìn)程的ID。
WUNTRACED:若實(shí)現(xiàn)支持作業(yè)控制,而pid指定的任一子進(jìn)程已經(jīng)暫停,且其狀態(tài)尚未報(bào)告,則返回其狀態(tài)。
返回值:
當(dāng)正常返回的時(shí)候,waitpid返回收集到的子進(jìn)程的進(jìn)程ID;
如果設(shè)置了選項(xiàng)WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進(jìn)程可收集,則返回0;
如果調(diào)用中出錯(cuò),則返回-1,這時(shí)errno會(huì)被設(shè)置成相應(yīng)的值以指示錯(cuò)誤所在;
當(dāng)pid所指示的子進(jìn)程不存在,或此進(jìn)程存在,但不是調(diào)用進(jìn)程的子進(jìn)程,waitpid就會(huì)出錯(cuò)返回,這時(shí)errno被設(shè)置為ECHILD;
獲取子進(jìn)程status:
- wait和waitpid,都有一個(gè)status參數(shù),該參數(shù)是一個(gè)輸出型參數(shù),由操作系統(tǒng)填充。
- 如果傳遞NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息。
否則,操作系統(tǒng)會(huì)根據(jù)該參數(shù),將子進(jìn)程的退出信息反饋給父進(jìn)程。- status不能簡(jiǎn)單的當(dāng)作整形來(lái)看待,可以當(dāng)作位圖來(lái)看待,具體細(xì)節(jié)如下(只研究status低16比特位):
![]()
我們可以通過(guò)status & 0x7f來(lái)判斷異常信號(hào)是否為0;若為0,則正常退出,然后可以通過(guò)(status >> 8) & 0xff來(lái)獲取子進(jìn)程返回值。sys/wait.h中提供了一些宏來(lái)簡(jiǎn)化這些操作:
if (WIFEXITED(status)) {
// 正常退出:((status) & 0x7f) == 0
// 打印退出碼:(status >> 8) & 0xff
printf("child return: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// 異常退出:((signed char) (((status) & 0x7f) + 1) >> 1) > 0
// 打印異常信號(hào)值:(status) & 0x7f
printf("child signal: %d\n", WTERMSIG(status));
}
我們用這段代碼來(lái)讀取子進(jìn)程的status,以獲取它的退出碼和異常信號(hào)值,以及父進(jìn)程收到的SIGCHLD信號(hào):
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
void catchsig(int sig)
{
printf("catch a sig : %d , pid->%d\n",sig,getpid());
}
int main()
{
signal(SIGCHLD,catchsig);
pid_t id = fork();
if(id<0)
{
printf("fork error!\n");
exit(-1);
}
else if(id==0)
{
int cnt = 3;
while(cnt)
{
printf("this is a child process! id-->%d pid-->%d ppid--> %d\n",cnt--,getpid(),getppid());
}
//exit(-1);
int i = 10/0;
}
sleep(2);
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(id>0)
//0-7:終止信號(hào) 15-8:退出狀態(tài)
printf("wait success:%d , sign number:%d , child exit code:%d\n",ret,(status & 0x7F),(status>>8 & 0xFF));
return 0;
}
運(yùn)行結(jié)果如下:
其實(shí),?進(jìn)程在終?時(shí)會(huì)給?進(jìn)程發(fā)SIGCHLD信號(hào),該信號(hào)的默認(rèn)處理動(dòng)作是忽略,?進(jìn)程可以?定義SIGCHLD信號(hào)的處理函數(shù),這樣?進(jìn)程只需專?處理??的?作,不必關(guān)??進(jìn)程了,?進(jìn)程終?時(shí)會(huì)通知?進(jìn)程,?進(jìn)程在信號(hào)處理函數(shù)中調(diào)?wait清理?進(jìn)程即可。一般情況下父進(jìn)程收到這個(gè)信號(hào)的默認(rèn)處理是忽略這個(gè)信號(hào),即就是不做任何處理。
3.進(jìn)程替換
替換原理:
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
替換函數(shù):
#include <unistd.h>
extern char **environ;
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 execvpe(const char *file, char *const argv[], *const envp[]);
這些函數(shù)如果調(diào)用成功則加載新的程序從啟動(dòng)代碼開始執(zhí)行,不再返回。
如果調(diào)用出錯(cuò)則返回-1,所以exec函數(shù)只有出錯(cuò)的返回值而沒有成功的返回值。
命名理解:
l(list) : 表示參數(shù)采用列表
v(vector) : 參數(shù)用數(shù)組
p(path) : 有p自動(dòng)搜索環(huán)境變量PATH
e(env) : 表示自己維護(hù)環(huán)境變量
int execl(const char *path, const char *arg, ...);
其中*path表示的是路徑,arg表示的是要執(zhí)行的程序,“…”表示的就是可變參數(shù)列表,即命令行上怎么執(zhí)行這里就寫入什么參數(shù)。必須以NULL作為參數(shù)列表的結(jié)束。
#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
if(fork()==0)
{
printf("command begin\n");
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("command fail\n");
exit(1);
}
waitpid(-1,NULL,0);
printf("wait child success\n");
return 0;
}
當(dāng)子進(jìn)程執(zhí)行完打印command begin的語(yǔ)句的時(shí)候,進(jìn)行進(jìn)程的替換。其中替換的是/usr/bin/ls,在命令行要輸入的是ls -a -l,將程序運(yùn)行起來(lái):
下面是這些函數(shù)的用法:
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 帶p的,可以使用環(huán)境變量PATH,無(wú)需寫全路徑
execlp("ps", "ps", "-ef", NULL);
// 帶e的,需要自己組裝環(huán)境變量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 帶p的,可以使用環(huán)境變量PATH,無(wú)需寫全路徑
execvp("ps", argv);
// 帶e的,需要自己組裝環(huán)境變量
execve("/bin/ps", argv, envp);
exit(0);
}
操作系統(tǒng)實(shí)際上只提供了一個(gè)接口那就是:execve,其他的函數(shù)都是對(duì)該接口封裝而成的庫(kù)函數(shù)。它們的底層都是使用execve來(lái)進(jìn)行實(shí)現(xiàn)的。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-720574.html
三、實(shí)現(xiàn)一個(gè)簡(jiǎn)單的shell
以下代碼利用程序替換實(shí)現(xiàn)了shell 的基本功能,包括重定向,退出碼等功能,后續(xù)還可以再補(bǔ)充。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-720574.html
#include<stdio.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define NUM 1024//緩沖區(qū)大小
#define COM_NUM 64//存放指令字符串的最大值
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUPUT_REDIR 2
#define APPEND_REDIR 4
char lineCommand[NUM];//輸入緩沖區(qū)
char *myargv[COM_NUM];//存放一個(gè)個(gè)的指令字符串
int exit_code=0;//退出碼
int exit_sign=0;//退出信號(hào)值
int redirType = NONE_REDIR;//讀方式
char* myFile = NULL;
//跳過(guò)空格
#define trimSpace(start) do{while(isspace(*start)) ++start;}while(0)
void commandCheck(char* commands)
{
assert(commands);
char *start = commands;
char *end = commands+strlen(commands);
while(start<end)
{
if(*start == '>')
{
*start = '\0';
start++;
if(*start == '>') // >> 表示追加重定向
{
start++;
redirType = APPEND_REDIR;
}
else // > 表示輸出重定向
{
redirType = OUPUT_REDIR;
}
trimSpace(start);
myFile = start;
break;
}
else if(*start == '<') // < 表示輸入重定向
{
*start = '\0';
start++;
redirType = INPUT_REDIR;
trimSpace(start);
myFile = start;
break;
}
else
start++;
}
}
//最新添加重定向功能!!!!!
int main()
{
while(1)
{
redirType = NONE_REDIR;
myFile = NULL;
printf("[wml @ my_bash path#]");
fflush(stdout);
//獲取輸入行內(nèi)容
char *s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
assert(s != NULL);
(void)s;
lineCommand[strlen(lineCommand)-1] = 0;
//重定向:
//"ls -a -l > "test.txt"" ----> "ls -a -l" > "test.txt"
//"ls -a -l >> "test.txt"" ----> "ls -a -l" >> "test.txt"
//"cat < "test.txt"" ----> "cat" < "test.txt"
commandCheck(lineCommand);
//切割字符串
myargv[0] = strtok(lineCommand," ");
int i = 1;
//添加顏色選項(xiàng)
if(myargv[0]!= NULL && strcmp(myargv[0],"ls") == 0 )
{
myargv[i++] = (char*) "--color=auto";
}
//讀取每個(gè)選項(xiàng)
while(myargv[i++] = strtok(NULL," "));
//解決工作路徑無(wú)法改變的問題
if(myargv[0]!=NULL && strcmp(myargv[0],"cd") == 0)
{
if(myargv[1]!=NULL)
chdir(myargv[1]);
continue;
}
//設(shè)置退出碼
if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo")==0 )
{
if(strcmp(myargv[1],"$?")==0)
printf("exit_code-->%d | exit_sign-->%d\n",exit_code,exit_sign);
else
printf("%s\n",myargv[1]);
continue;
}
#ifdef DEBUG
for(int i=0;myargv[i];i++)
printf("myargv[%d]:%s\n",i,myargv[i]);
#endif
pid_t id = fork();
assert(id!=-1);
if(id==0)
{
switch(redirType)
{
case NONE_REDIR:
break;
case INPUT_REDIR:
{
int fd = open(myFile,O_RDONLY);
if(fd<0) {perror("open");return 1;}
dup2(fd,0);
}
break;
case OUPUT_REDIR:
case APPEND_REDIR:
{
umask(0);
int flag = O_WRONLY | O_CREAT;
if(redirType == APPEND_REDIR) flag |= O_APPEND;
else flag |= O_TRUNC;
int fd = open(myFile,flag,0666);
if(fd<0){perror("open");return 2;};
dup2(fd,1);
}
break;
default:
printf("bug!\n");
break;
}
execvp(myargv[0],myargv);
_exit(1);
}
int status=0;
pid_t ret = waitpid(id,&status,0);
assert(ret!=-1);
(void)ret;
exit_sign = (status & 0x7f);
exit_code = (status>>8) & 0xff;
}
return 0;
}
到了這里,關(guān)于認(rèn)識(shí)環(huán)境變量和進(jìn)程替換,實(shí)現(xiàn)一個(gè)簡(jiǎn)易的shell的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!