文章目錄
- 一、行程相關概念
- 1、程式和行程
- 2、并發
- 3、單道程式設計
- 4、多道程式設計
- 5、CPU和MMU
- 6、行程控制塊PCB
- 7、行程狀態
- 二、環境變數
- 1、常見環境變數
- 1.1 PATH
- 1.2 SHELL
- 1.3 TERM
- 1.4 LANG
- 1.5 HOME
- 2、相關函式
- 2.1 getenv
- 2.2 setenv
- 2.3 unsetenv
- 三、行程控制
- 1、fork
- 2、getpid
- 3、getppid
- 4、getuid
- 5、getgid
- 6、行程共享
- 7、gdb除錯
- 三、exec函式
- 1、execlp
- 2、execl
- 四、回收子行程
- 1、孤兒行程
- 2、僵尸行程
- 3、wait函式
- 4、waitpid函式
- 5、案例
- 五、IPC方法
- 1、管道
- 1.1 pipe
- 1.2 案例
- 2、共享存盤映射
- 2.1存盤映射I/O
- 2.2 mmap
- 2.3 munmap
- 2.4 常見問題
- 2.5 案例
- 2.6 mmap父子行程通信
- 2.7 匿名映射
- 2.8 mmap無血緣關系行程間通信
一、行程相關概念
1、程式和行程
- 程式:編譯好的二進制檔案;
- 行程:抽象概念,活躍的程式,占用資源,在記憶體中執行;
- 同一程式可加載為不同的行程;
2、并發
一個時間段中有
多個行程都處于啟動到結束之間的狀態,在任一時刻都只有一個行程在運行,
3、單道程式設計
一次只能運行一個程式;
4、多道程式設計
- 同時存放幾道相互獨立的程式;
- 首先必須要有支持該硬體的基礎;
- 時鐘中斷是其基礎:強制讓行程讓出cpu,1秒可執行10億條指令;
5、CPU和MMU
cpu作業原理
將命令傳入到預取器中,后轉到譯碼器進行譯碼,在傳入算數邏輯單元執行,再將處理好的資料傳到暫存器堆,最后再傳回暫存器;

存盤介質

MMU作業原理
- 位于cpu內部;
- 虛擬記憶體與物理記憶體的映射;
- 設定修改記憶體訪問級別;
6、行程控制塊PCB
每個行程在內核中都有一個PCB來維護行程相關的資訊,Linux內核的行程控制塊是task_struct結構體;
struct task_struct
- 行程id;
- 狀態;
- 行程切換時需要保存和恢復的一些cpu暫存器;
- 描述虛擬地址空間的資訊;
- 描述控制終端的資訊;
- 當前作業目錄;
- umask掩碼;
- 檔案描述符表,包含很多指向file結構體的指標;
- 和信號相關的資訊;
- 用戶id和組id;
- 會話和行程組;
- 行程可以使用的資源上限;

7、行程狀態
初始態、就緒態、運行態、掛起態、終止態;
二、環境變數
- 在作業系統中用來指定作業系統運行環境的一些引數;
- 位置:位于用戶區,高于stack的起始位置;
- 引入環境變數表:需要宣告環境變數,
extern char ** environ- 存盤形式:
char *[] environ,NULL為結尾;
特征
- 字串;
- 格式:名=值[:值](多個值,則用冒號隔開);
- 值用來描述行程環境資訊;
1、常見環境變數
1.1 PATH
- 可執行檔案的搜索路徑,
- 例:
ls命令也是一個程式,執行它不需要提供完整的路徑名/bin/ls,然而通常我們執行當前目錄下的程式a.out卻需要提供完整的路徑名/a.out,由于PATH環境變數的值里面包含了Is命令所在的目錄/bin,卻不包含a.out所在的目錄,PATH 環境變數的值可以包含多個目錄,用:號隔開,- 在Shell中用echo命令可以查看這個環境變數的值:
$ echo $PATH;
1.2 SHELL
當前shell:
/bin/bash;
1.3 TERM
1.4 LANG
1.5 HOME
2、相關函式
2.1 getenv
獲取環境變數值
char *getenv(const char *name);
char *secure_getenv(const char *name);
/*
* return: 回傳一個指向環境中該值的指標,如果不匹配則回傳NULL;
*/
2.2 setenv
設定/添加環境變數的值;
int setenv(char *name, const char *value, int overwrite)
/*
* @param overwrite: 1時,覆寫原環境變數,0時,不覆寫;
* return:成功回傳0,失敗回傳-1;
*/
2.3 unsetenv
洗掉環境變數name的定義
int unsetenv(const char *name)
/*
* return: 成功回傳0,失敗回傳-1,name不存在仍回傳0
*/
三、行程控制
1、fork
通過復制創建一個子行程
pid_t fork(void);
/*
* return: 失敗回傳-1,成功回傳:父行程回傳子行程的ID以及子行程回傳0
*/
2、getpid
回傳呼叫行程的
行程ID, (生成唯一臨時檔案名的例程經常使用此方法)
pid_t getpid(void);
3、getppid
回傳呼叫行程的
父行程的行程ID,
pid_t getppid(void);
4、getuid
回傳呼叫行程的真實
用戶ID,
uid_t getuid(void);
uid_t geteuid(void);
// 獲取當前行程有效id
5、getgid
回傳呼叫行程的真實
組ID,
gid_t getgid(void);
6、行程共享
父子行程間,遵循
讀時共享寫時復制原則;
fork后的相同之處:
.data;.text;- 堆疊;
- 堆;
- 環境變數;
- 用戶ID;
- 宿主目錄;
- 行程作業目錄;
信號處理方式;
相同之處:
- 行程ID;
- fork回傳值;
- 父行程ID;
- 行程運行時間;
- 定時器;
- 未決信號集;
共享:
- 檔案描述符(打開檔案的結構體);
mmap建立的映射區,
7、gdb除錯
gdb除錯只能跟蹤一個行程,可在fork函式呼叫前,通過gdb除錯跟蹤行程或其子行程,默認跟蹤父行程;
set follow-fork-mode child命令設定gdb在fork之后跟蹤父行程;set follow-fork-mode parent設定跟蹤父行程;
三、exec函式
子行程要呼叫一種exec函式執行另一個程式,并退出該程式;
- l:命令列引數串列;
- p:搜索file時使用path變數;
- v:使用命令引數陣列;
- e:使用環境變數陣列;
1、execlp
加載一個行程,借助PATH環境變數;
int execlp(const char *file, const char*arg...);
/*
eg: execl('ls', 'ls', '-l', '-a', NULL);
* @param file: 要加載的程式名,配合PATH來使用,當在PATH中所有目錄搜索后無該引數則回傳-1;
* return: 只在錯誤的時候回傳-1
*/
2、execl
加載一個行程;與
execpl的差別就在于第一個引數;
int execl(const char *path, const char *arg, ...);
/*
* @param path: 路徑+程式名;
* return: 成功無回傳,失敗回傳-1;
*/
四、回收子行程
1、孤兒行程
父行程先于子行程結束,則子行程為孤兒行程,子行程的父行程成為init行程,稱為
init行程領養孤兒行程;
2、僵尸行程
- 行程終止,父行程尚未識訓,子行程殘留PCB在內核中;
- kill不能終止僵尸行程,因為僵尸行程已終止,
3、wait函式
父行程可呼叫wait獲取,在清除該行程,
- 阻塞等待子行程退出;
- 回收子行程殘留資源;
- 獲取子行程結束狀態(退出原因),
pid_t wait(int *status);
/*
* @param status: 為傳出引數,子行程狀態;
* - WIFEXITED為(status)非0 - 行程正常結束;
* - WEXITSTATUS(status) - 上一個宏為真,則獲取行程退出狀態,exit的引數;
* - WIFSIGNALED(status) - 行程例外終止;
* - WTERMSIG(status) - 上一個宏為真,則取得使行程終止的那個信號的編號;
* return: 回傳終止子行程的行程ID; 出錯時,回傳-1;
*/
4、waitpid函式
作用與
wait相同,指定pid行程清理,可不阻塞;
pid_t waitpid(pid_t pid, int *status, int options);
/*
* @param pid:
* >0 - 回收指定ID的子行程;
* -1 - 回收任意子行程(=wait);
* 0 - 回收和當前呼叫waitpid一個組的所有子行程;
* <-1 -回收指定行程組內的任意子行程;
* return: 成功回傳清理的子行程,失敗回傳-1(無子行程),回傳0引數3為WONHANG,且子行程正在運行;
*/
5、案例
#include<iostream>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){
pid_t pid, wpid;
int status;
//for(int i=0; i<5; i++)
pid = fork();
if(pid == 0){
// 子行程, getppid獲取父行程ID
std::cout << "我是子行程,父行程為:" << getppid() << std::endl;
//sleep(20);
exit(1);
}else if(pid == -1){
// 失敗
std::cout << "error" << std::endl;
}else{
wpid = wait(&status);
// 例外退出
if(WIFSIGNALED(status) != 0){
std::cout << "子行程的例外退出狀態:" << WTERMSIG(status) << std::endl;
}
// 正常退出
if(WIFEXITED(status)){
std::cout << "子行程的正常退出狀態:" << WEXITSTATUS(status) << std::endl;
}
while(1){
std::cout << "父行程ID:" << getpid() << "子行程為L: "<< pid << std::endl;
sleep(1);
}
}
return 0;
}
五、IPC方法
Linux下行程是相互
獨立的,不能相互訪問,若要交換資料則需要通過內核,在內核開辟一塊緩沖區,將行程1把資料從用戶空間拷到內核緩沖區,行程2再從內核緩沖區把資料讀走,該機制為IPC行程間通信,
常用的通信方式:
- 管道 - 最簡單;
- 信號 - 開銷最小;
- 共享映射區 - 無血緣關系;
- 本地套接字 - 最穩定,
1、管道
- 其本質是一個
偽檔案(實為內核緩沖區4k);- 由倆個檔案描述符參考,一個
讀端,一個寫端;- 規定資料從管道的
寫端流入管道,從讀端流出;
局限性:
- 資料自己讀
不能自己寫;- 資料被讀走,便不在管道中存在,
不可反復讀取;- 由于管道采用
半雙工通信方式,因此,資料只能再一個方向上流動;- 只能再有
公共祖先的行程間使用管道;
常用通信方式:
- 單工通信:只能發生信號 - 遙控器;
- 半雙工通信:單向發生或接收 - 微信;
- 全雙工通信:雙向發生接收 - 通話;
1.1 pipe
創建一個管道;
int pipe(int pipefd[2]);
/*
* @param pipefd:
* pipefd[0]指的是管道的讀端
* pipefd[1]指到管道的寫入端
* return: 成功回傳0,失敗回傳-1
*/
1.2 案例
#include<stdlib.h>
#include<unistd.h>
#include<iostream>
#include<cstring>
/*
* 1、創建子行程,管道
* 2、將子行程設為讀、父行程設為寫
* 3、通過管道實作行程間通信
* */
int main(int argc, char *argv[]){
pid_t pid;
int fd[2];
int ret = pipe(fd);
if(ret == -1){
std::cout << "pipe create error..." << std::endl;
exit(1);
}
pid = fork();
if(pid == -1){
std::cout << "child process error..." << std::endl;
exit(1);
}else if(pid == 0){ // 子行程
std::cout << "child process id: " << getpid() << std::endl;
close(fd[1]); // 關閉寫操作
char buf[1024];
ret = read(fd[0], buf, 1024);
if(ret == -1){
std::cout << "file read error..." << std::endl;
exit(1);
}
// 將讀到的資料輸出
write(STDOUT_FILENO, buf, ret);
}else{ // 父行程
close(fd[0]); // 關閉讀操作
std::string myStr = "test pipe\n";
write(fd[1], myStr.c_str(), myStr.size());
}
return 0;
}
2、共享存盤映射
2.1存盤映射I/O
- 使一個磁盤檔案與存盤空間中的一個緩沖區相映射(相當于從緩沖區取資料),類似于,將資料存入緩沖區,則相應位元組就自動寫入檔案,即使用
I/O地址操作;- 首先應通知內核,將一個指定檔案映射到存盤區域,可通過
mmap函式實作;
2.2 mmap
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
/*
* @param addr: 建立映射區的首地址,有Linux內核指定,使用時,傳入NULL即可;
* @param length: 指定映射的長度;
* @param prot:映射區權限;
* - PROT_NONE:
* - PROT_EXEC: 可能被執行;
* - PROT_READ: 讀取;
* - PROT_WRITE: 寫;
* - PROT_NONE: 無法訪問;
* @param flags: 標志位引數(用于設定更新物理區域、設定共享、創建匿名映射區)
* - MAP_SHARED:會將映射區所做的操作反映到物理設備上;
* - MAP_PRIVATE:映射區所做的修改不會反映到物理設備;
* @param fd:用來建設映射區的檔案描述符;
* @param offset:映射檔案的偏移(4k的整數倍);
* return: 回傳一個指向映射區域的指標,失敗回傳MAP_FAILED;
*/
2.3 munmap
系統呼叫洗掉指定地址范圍的映射,并導致對在產生
無效記憶體參考的范圍內的地址, 該區域也會自動取消映射行程終止,
int munmap(void *addr, size_t length);
/*
* @param addr: map的回傳值;
* return: 失敗回傳0;
*/
2.4 常見問題
- 映射區大小不能為0;
- 創建映射區的權限要 <= 檔案打開的權限,映射區的創建隱含著對檔案的讀操作;
offset必須是4k的整數倍;- 映射區的釋放與檔案關閉無關,只要映射
建立成功,檔案即可關閉;munmap傳入的第一個引數一定是mmap回傳的地址;
2.5 案例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
/*
* 1、打開檔案,并獲取檔案大小
* 2、創建映射區
* 3、
* */
int main(int argc, char *argv[]){
char *p = NULL;
int fd = open("test.txt", O_CREAT|O_RDWR, 0644); // 打開/創建檔案
if(fd < 0){
std::cout << "open error..." << std::endl;
exit(1);
}
// 拓展檔案大小
int ret = ftruncate(fd, 4);
if(ret == -1){
std::cout << "ftruncate error..." << std::endl;
exit(1);
}
// 創建映射區,注意c++中需要強轉
p = (char *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
std::cout << "mmap error..." << std::endl;
exit(1);
}
std::cout << "-----------------------" << std::endl;
strcpy(p, "test\n");
ret = munmap(p, 4);
if(ret == -1){
std::cout << "munmap error..." << std::endl;
exit(1);
}
std::cout << "over............." << std::endl;
close(fd);
return 0;
}
2.6 mmap父子行程通信
有
血緣關系的行程可通過mmap建立的映射區來完成資料通信,需要設定對應的flag:
MAP_PRIVATE:(私有映射)父子行程各自獨占映射區;MAP_SHARED:(共享映射)父子行程共享映射區;
- 結論:
- 打開的檔案;
- mmap建立的映射區,需要用
MAP_SHAPED
案例:
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/mman.h>
int t_val = 1;
/*
*結論:
* 打開的檔案;
* mmap建立的映射區,需要用`MAP_SHAPED`
*/
int main(int argc, char *argv[]){
pid_t pid; // 存盤fork回傳的子行程ID
int fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open error");
exit(1);
}
/*
* 洗掉臨時檔案目錄項,讓所有占用該檔案的行程都結束后被洗掉
* */
unlink("temp.txt");
ftruncate(fd, 4);
int *p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
perror("mmap error");
exit(1);
}
close(fd); // 映射區建立好即可關閉
pid = fork();
if(pid == 0){
// 子行程回傳
*p = 1000;
t_val = 200;
std::cout << "child: " << getpid() << "*p: " << *p << "t_val: " << t_val << std::endl;
}else if(pid < 0){
perror("fork error");
exit(1);
}else{
sleep(1);
std::cout << "parent: " << getppid() << " child: " << pid << " *p: " << *p << " t_val: " << t_val << std::endl;
wait(NULL);
int ret = munmap(p, 4);
if(ret == -1){
perror("munmap error");
exit(1);
}
}
std::cout << "---------------" << std::endl;
return 0;
}
2.7 匿名映射
無需依賴一個檔案即可創建映射區,且需要標志位來flag來指定;
- flag只需或上:
MAP_ANONYMOUS/MAP_ANON;- 檔案描述符使用
-1代替;- 是linux系統特有的;
類UNIX系統中
- 需要借助
fd = open("/dev/zero", O_RDWR)- 該檔案無大小;
2.8 mmap無血緣關系行程間通信
寫檔案
#include<iostream>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<cstring>
#include<stdio.h>
/*@ 學生結構體 */
typedef struct STU{
int id;
char name[10];
char sex;
}stu;
/* 寫檔案
*
* */
int main(int argc, char *argv[]){
STU student = {10, "xiaoming", 'm'};
char *mm;
if(argc < 2){
std::cout << "./a.out file_shared" << std::endl;
exit(-1);
}
// 打開檔案
int fd = open(argv[1], O_RDWR|O_CREAT, 0644);
ftruncate(fd, sizeof(student));
// 建立映射區
mm = (char *)mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED){
perror("mmap error");
exit(-1);
}
close(fd);
while(1){
memcpy(mm, &student, sizeof(student));
student.id++;
std::cout << "write..." << std::endl;
sleep(1);
}
munmap(mm, sizeof(student));
return 0;
}
讀檔案
#include<iostream>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<cstring>
#include<sys/mman.h>
#include<stdio.h>
/*@ 學生結構體 */
typedef struct stu{
int id;
char name[10];
char sex;
}STU;
/* 讀檔案
*
* */
int main(int argc, char *argv[]){
STU student;
STU *st;
if(argc < 2){
std::cout << "a.out file_sharead\n" << std::endl;
exit(-1);
}
// 只讀模式,打開檔案
int fd = open(argv[1], O_RDONLY);
if(fd == -1){
perror("open error");
exit(-1);
}
// 建立映射,建立通信
st = (STU *)mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
if(st == MAP_FAILED){
perror("mmap error");
exit(-1);
}
close(fd);
// 列印資料
while(1){
std::cout << "id: " << st->id << "name: " << st->name << "性別: " << st->sex << std::endl;
sleep(2);
}
// 終止映射
munmap(st, sizeof(student));
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/300708.html
標籤:其他
上一篇:物理地址(硬體地址)
下一篇:阿里云前端[Vue.js / nuxt.js]?后端【node.js + mySql + PM2 +GIt】上線部署流程
