1.先看代碼 && 現象
?我們用exec*函數執行新的程序,
exec*系列的函數,執行完畢后,后續的代碼不見了,因為被替換了。
execl的返回值可以不關心了,只要替換成功,就不會向后繼續運行,只要繼續運行了,一定是替換失敗了!
2.解釋原理
?進程 = 內核數據結構 + 代碼數據
進程的程序替換,有沒有創建新的進程?? 沒有的
站在被替換進程的角度:本質就是這個程序被加載到內存了!
怎么加載?exec*類似于Linux上的加載函數
3.將代碼改成多進程版
fork創建子進程,讓子進程自己去替換,wait等待
創建子進程,讓子進程完成任務:
- 讓子進程執行父進程代碼的一部分
- 讓子進程執行一個全新的程序
4.使用所有的替換方法,并且認識函數參數的含義
下面我們查的是這些替換方法,用man 3 execl
3手冊是c語言的標準庫GNU標準,所以我們實際查的是幾個內含系統調用的c語言函數。
?
?這是6個exec*系列函數。
?第一個execl函數:
path:我們要執行的程序,需要帶路徑(怎么找到程序你得告訴我)
exec后面的l -- list:就是要存放的選項列表,在命令行中怎么執行,你就怎么傳參!ls -a -l?
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
路徑代表你想執行誰,選項代表你想怎么執行!!!
帶l的是我們要傳入一個列表選項,那么帶v的呢?
?v:有動態數組的意思。
?明顯是要我們傳入一個數組,這個數組就包含我們想要的選項,
?帶p的exec函數:
用戶可以不傳要執行的文件的路徑(但是文件名要傳),直接告訴exec*,我要執行誰就行
p:查找這個程序,系統會自動在環境變量PATH中進行查找。
?
?e:environment環境變量。
envp[]:整體替換所有的環境變量!
- 用全新的給子進程。
- 用老的環境變量給子進程,environ。
- 老的環境變量稍微修改,給子進程。
上面的程序替換 ,我們替換的都是系統命令,可不可以替換我們自己寫的程序呢?
支持不同的應用場景!!!
當然可以。 而且可以替換用任何語言寫的無論是c++,java,python等語言都可進行替換,替換之后原程序pid不會改變,創建的子進程也不會改變,因為進程的替換,只是代碼和數據的替換,不影響原程序的pcb。
系統調用接口execve。
?上面的函數最終都將會走到系統調用接口。
5.寫一個自己的Shell(簡易版)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h> #define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{p += (strlen(p)-1);while(*p != '/')--p;}while(0) char cwd[SIZE * 2];
char* gArgv[NUM];
int lastcode = 0; void Die()
{ exit(1);
}
const char* GetHome()
{ const char* home = getenv("HOME"); if(home == NULL) return "None"; return home;
}
const char* GetUserName()
{ const char* username = getenv("USER");if(username == NULL) return "None";return username;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}
//commandline:output
void MakeCommandLineAndPrint()
{char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();const char* GetCwd()
{const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}
//commandline:output
void MakeCommandLineAndPrint()
{char line[SIZE];const char* username = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();SkipPath(cwd);snprintf(line, sizeof(line), "[%s@%s %s]>", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);printf("%s", line);fflush(stdout);
}
int GetUserCommand(char command[], size_t n)
{char* s = fgets(command, n, stdin);if(s == NULL) return -1;command[strlen(command) - 1] = ZERO;return strlen(command);
}
void SplitCommand(char command[], size_t n)
{(void)n;//"ls -a -l" -> "ls", "-a", "-l"gArgv[0] = strtok(command, SEP);int index = 1; while((gArgv[index] = strtok(NULL, SEP)))index++;// done, 故意寫成=,表示先賦值,在判斷,分割之后,strtok會返回NULL,剛好讓gArgv最后一個元素是NULL,并且while判斷結束
}void ExecuteCommand()
{pid_t id = fork();if(id < 0) Die();else if(id == 0){//childexecvp(gArgv[0], gArgv);exit(errno);}else {//fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);}}
}
void Cd()
{const char* path = gArgv[1];if(path == NULL) path = GetHome();//path一定存在chdir(path);//刷新環境變量char temp[SIZE * 2];getcwd(temp, sizeof(temp));snprintf(cwd, sizeof(cwd), "PWD=%s", temp);putenv(cwd);//ok
}
int CheckBuildin()
{int yes = 0;const char* enter_cmd = gArgv[0];if(strcmp(enter_cmd, "cd") == 0){yes = 1;Cd();}else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0){yes = 1;printf("%d\n", lastcode);lastcode = 0;}return yes;
}
int main()
{int quit = 0;while(!quit) {//1.我們需要輸出一個命令行MakeCommandLineAndPrint();//2.獲取用戶命令行字符串char usercommand[SIZE];int n = GetUserCommand(usercommand, sizeof(usercommand));if(n <= 0) return 1;//3.命令行字符串分割SplitCommand(usercommand, sizeof(usercommand));//4.檢測命令是否是內鍵命令n = CheckBuildin();if(n) continue;//5,執行命令ExecuteCommand();}return 0;
}