切板中的內容輸出到文件### 進程相關概念
程序:編譯好的二進制文件,在磁盤上,不占用系統資源(不包括磁盤)。(劇本)
進程:占用系統資源,是程序的一次運行。(戲劇)
一個程序可以產生多個進程,一個進程可以調用多個程序
并發:并行執行
單道程序設計:DOS系統
多道程序設計:時鐘中斷
中央處理器CPU
存儲介質:按照容量從大到小:
硬盤->內存->cache(高速緩存)->寄存器
預取器:從cache中取出指令
譯碼器:解析指令
算數邏輯單元(ALU):只會+
和<<
寄存器堆:ALU操作的寄存器扎堆的地方
然后再將寄存器中的值返回給cache
MMU
MMU位于CPU內部:負責虛擬內存和物理內存之間的映射,設置修改內存訪問的級別(CPU中設置了0-3四個等級,Linux系統中用到0級內核區和3級用戶區)
每產生一個進程產生一個虛擬內存:可用的地址空間
每次最小分配物理內存4K(一個page)
同一個程序的不同進程的kernel區內存映射到同一個空間,但是使用的是不同的PCB
PCB
處于kernel區,進程描述符\進程控制塊,實際上是一個task_struct
結構體,里面有很多的成員
- 進程的id,無符號整數
- 進程狀態:初狀態、就緒狀態、運行狀態、掛起狀態、終止狀態
- 進程切換時候需要保存和恢復的一些寄存器
- 描述虛擬地址空間的信息
- 描述控制終端的信息
- 當前進程的工作目錄
umask
掩碼- 文件描述符表
- 和信號相關的信息
- 用戶id和組id
- 會話和進程組
- 進程可以使用的資源上限
ulimit -a
環境變量
Linux系統是多用戶多任務的開源操作系統
用戶操作計算機的時候運行的一些信息通過環境變量進行設置
- 字符串
char * environ[]
,存儲在用戶區,高于stack
的起始位置 - 統一的存儲格式:
名字=值[:值]
- 值用來描述進程環境信息
以shell
為例,所使用的環境變量為PATH
,在解析命令的時候按照PATH
中的內容從前往后逐個目錄進行查找,因此如果希望使用新版本軟件應該把新版本軟件的環境變量向前移動
SHELL 所使用的命令解析器在哪里
HOME 家目錄在哪里
LANG 使用的是什么語言
TERM 所使用的終端類型,圖形界面所使用的一般是xterm,可以顯示漢字,字符界面一般不可以
通過程序打印所有的環境變量:
#include<stdio.h>extern char ** environ;//引入環境變量表int main(void)
{int i;for(int i=0;environ[i]!=NULL;++i){printf("%s\n",environ[i]);}return 0;
}
相關函數
getenv
手冊第三章setenv
unsetenv
刪除環境變量:即使沒有那個環境變量也會返回成功,只有當參數為已經有的環境變量的非法格式例如:name=
才會報錯,例如:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main()
{const char* name="123ABC";char *val;setenv(name,"123",1);printf("%d\n",unsetenv("123ABC=1")); //-1printf("%d\n",unsetenv("123ABC")); //0return 0;
}
進程控制
創建進程的方法:
- 運行可執行程序
- 通過
fork
函數創建子進程
fork函數創建子進程
#include<unistd.h>
pid_t fork(void)
fork
有兩個成功返回值
如果子進程創建失敗則返回-1,并且將錯誤信息保存在erron中,我們可以使用perror輸出錯誤信息。
如果子進程創建成功則會在父進程中返回子進程的ID,在子進程中返回0
父進程的fork
返回子進程ID,子進程的fork
返回值為0,通過對返回值的判斷處于哪個進程
可執行文件的父進程是bash
創建單個進程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main()
{printf("begin:\n");pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){printf("This is son process,pid = %d \n",(int)getpid());printf("My father process pid = %d \n",(int)getppid());}else{printf("This is father process,pid = %d \n",(int)getpid());printf("My father process pid = %d \n",(int)getppid());sleep(1);}printf("end\n");return 0;
}
循環創建N個子進程
如果直接使用循環進行創建,則n層循環會創建2n-1個子進程,這顯然不是我們需要的。
因此我們需要在子進程中直接跳出循環,這樣就不會產生過多的子進程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main(int argc,char* argv[])
{if(argc<2){printf("too few arguments\n");exit(1);}if(argc>2){printf("too few arguments\n");exit(1);}int i,limit=argv[1][0]-'0';printf("begin:\n");for(i=0;i<limit;++i){pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){printf("This is %dth son process,pid = %d \n",i+1,(int)getpid());printf("My father process pid = %d \n",(int)getppid());break;}
}printf("end\n");return 0;
}
在《APUE》中說到如果不加控制的話父進程有98%的可能性獲得CPU的控制權(不過我的電腦上并不是這樣),由內核的調度算法決定
getuid
獲取當前進程實際用戶IDuid_t getuid(void);
獲取當前進程有效用戶IDuid_t geteuid(void);
getgid
獲取當前進程實際用戶組IDgid_t getgid(void);
獲取當前進程有效用戶組IDgid_t getegid(void);
進程共享
父子進程相同的:全局變量、.data,.text,棧、共享庫、堆、環境變量、用戶ID、宿主目錄、進程工作目錄、信號處理方式都是相同的
父子進程對于前面的變量的處理:讀時共享寫時復制
如果子進程只對前面的數據進行讀取,則和父進程共享同一個變量,如果對前面的數據進行修改(寫,改變),則復制一份新的,指針的話會制定一個新的地址。
父子進程不同的:進程ID,fork返回值,父進程ID,進程運行時間,鬧鐘(定時器),未決信號集
進程運行時間:子進程的運行時間為父進程fork()調用時間
父子進程共享:
- 文件描述符
mmap
建立的映射區(進程間的通信)
我自己寫了一個測試程序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main(int argc,char* argv[])
{int fd=open("test",O_CREAT | O_RDWR | O_TRUNC,0644);pid_t fid=getppid();if(argc<2){printf("too few arguments\n");exit(1);}if(argc>2){printf("too few arguments\n");exit(1);}int i,limit=argv[1][0]-'0';printf("begin:\n");for(i=0;i<limit;++i){pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){break;}}char buffer[100];memset(buffer,0,sizeof(buffer));int sz=sprintf(buffer,"This is %dth son process,pid = %d \n",i+1,(int)getpid());write(fd,buffer,sz);if(getppid()==fid){close(fd);printf("end\n");}return 0;
}
gdb調試
使用gdb調試的時候,gdb只能跟蹤一個進程,可以在fork函數調用之前,通過執行設置gdb調試工具跟蹤父進程或者是跟蹤子進程,默認跟蹤父進程。
set follow-fork-mode child
命令設置gdb在fork之后跟蹤子進程set follow-fork-mod parent
設置跟蹤父進程- 注意一定在
fork
函數調用前設置才有效