進程
- 進程是一個可并發執行的具有獨立功能的程序關于某個數據集合的一次執行過程,也是操作系統執行資源分配和保護的基本單位。
- 程序的一次執行就是一個進程
- 一個程序可以派生多個進程
- 多個不同程序運行的時候,也會有多個相對應的進程與其相互對應
- 進程是動態的,有始有終,有自己的生命周期,有進程狀態的變化
程序與進程的區別
- 程序是靜止的,無生命的,進程是活動的
- 程序是可以脫離機器長期存在,而進程是執行了的程序
- 程序不具備并發性,不占據系統的資源,進程則相反,具備并發性、會占據內存空間,并且會受到其他進程的制約和影響
- 一個程序對應很多的進程
進程的狀態
- 運行狀態
- 就緒狀態 缺少cpu
- 等待狀態 缺少IO資源,等待
相關命令
- 使用命令 ps -ax 查看進程的相關狀態;比如進程號 、 進程的狀態、
進程樹的形成
- 計算機啟動之后,BIOS從磁盤引導扇區加載系統引導程序,它將Linux系統裝入內存,并且跳到內核處開始執行,Linux內核就執行初始化工作:初始化硬件、初始化內部數據結構、建立進程0
- 進程0創建進程1,進程1是以后所有創建進程的祖先,它負責初始化所有的用戶進程。進程1創建shell,shell進程顯示提示符,等待命令的輸入
- init進程ID為1,通常是init進程,在自檢過程結束的時候由內核調用
- init進程不會終止
- init進程是一個普通的用戶進程(與交換進程不同,他不是內核中的系統進程,但是它以超級用戶特權執行)
進程的創建
- 任何進程的創建,都是基于現有的進程
- 進程的創建可以使用fork 和 exec
- fork 視為新的進程分配響應的數據結構,并且將父進程的相應上下文信息復制過來
- exec 將可執行文件的正文和數據轉入內存替代先前的內容(從父進程復制過來的數據),并開始執行正文段,將當前進程替換為一個新的進程
進程的終止
- 使用exit()
- exit釋放除了task_struct以外的所有的上下文,父進程收到子進程的終結信號之后,釋放子進程的task_struct
- vfork調用之后,將立即調用exec,這樣就不需要拷貝父進程的所有的頁表,因此比fork快
- pid_t fork(void);
- pid_t vfor(void);
- 頭文件 unistd.h
fork系統調用
- 當fork函數調用成功時,對父進程返回子進程的PID,對子進程返回0
- 調用失敗的時候,父進程返回-1,并沒有子進程的創建
- fork函數返回 兩個參數,分別給父進程和子進程
pid_t new_pid;
new_pid = fork();
switch(new_pid){case -1://Error!break;case 0://Child processbreak;default://parent processbreak;
}
exec系統調用
- exec是用來執行一個可執行文件來代替當前進程的執行鏡像
- int execl(const char * path,const char * arg, ...)
- int execlp(const char * file,const char * arg, ...)
- int execle(const char *path, const char * arg,...,const char *envp[])
- ?int execv(const char *path,const char * argv[])
- int execve(const char * filename,char * const argv[],char * const envp[])
- int execvp(const char *file,char * const argv[])
函數的原型
- int execve(const char * filename,char * const argv[],char * const envp[])
- filename? ?執行的文件
- argv? ? ? ? ? 傳遞給文件的參數
- envp? ? ? ? ?傳遞給文件的環境變量
- 當參數path所指定的文件替換原進程的執行鏡像之后,文件的path開始執行,參數argv和envp便傳遞給進程
- l 表示list
- v表示vector
- e 可以傳遞新進程環境變量 execle execve
- p 可執行文件查找方式為文件的名字 execlp execvp
char* envp[] = {"PATH = /tmp","User = lei","STATUS = testing", nullptr};char* argv_execv[] = {"echo","executed by execv", nullptr};char* argv_execvp[] = {"echo","executed by execvp", nullptr};char* argv_execve[] = {"env", nullptr};if (execl("/bin/echo","echo","executed by execl", nullptr)<0)perror("Err on execl");if (execlp("echo","echo","executed by execlp", nullptr)<0)perror("Err on execlp");if(execle("/user/bin/env","env", nullptr,envp)<0)perror("Err on execle");if (execv("/bin/echo",argv_execv)<0)perror("Err on execv");if (execvp("echo",argv_execvp))perror("Err on execvp");if(execve("/user/bin/ebv",argv_execve,envp)<0)perror("Err on execve");
函數
- ?頭文件 <sys/types.h> <unistd.h>
- pid_t getpid(void);返回調用進程的進程ID
- pid_t getppid(void);但會調用進程的父進程ID
- uid_t getuid(void);返回調用進程的實際用戶ID
- uid_t geteuid(void);返回進程的有效用戶ID
- gid_t getgid(void);返回進程的實際組ID
- gid_t getegit(void);調用進程的有效組ID
#include <iostream>
#include <sys/types.h>
#include <unistd.h>int main(){pid_t cld_pid;cld_pid = fork();if (cld_pid == 0){std::cout << "This is child process.\n" << std::endl;printf("My PID(child) is %d\n",getpid());printf("My child PID is %d\n",cld_pid );}else{std::cout << "This is parent process.\n" << std::endl;printf("My PID(parent) is %d\n",getpid());printf("My child PID is %d\n",cld_pid );}return 0;
}
?exit系統調用
- _exit 直接結束進程,清除其內存使用空間,并且清除其在內核中的數據結構
- exit 函數在調用之前會檢查文件的打開情況,把文件的緩存區的內容寫會文件中,比如調用printf()函數
wait系統調用
- wait函數 用于使父進程阻塞,直到一個進程結束或者該進程收到一個指定信號為止
- 調用wait或者waitpid的進程可能會:
- 阻塞:如果其所有的進程還在運行
- 帶子進程的終止狀態立即返回(如果一個子進程已經終止,正等待父進程存取其終止狀態)
- 出錯立即返回(如果沒有任何子進程)
- pid_t wait(int *status);? status返回子進程退出時的狀態信息
- pid_t waitpid(pid_t pid,int *status,int options);//等待指定的進程id
- 頭文件 <sys/types.h> 和 <sys/wait.h>
- 兩個函數返回數值:如果成功返回子進程的ID號,如果出現問題,返回-1
wait和waitpid函數的區別
- 在一個子進程終止之前,wait使其調用者阻塞,而waitpid有一個選項,可以不阻塞調用者
- waitpid并不等待第一個終止的子進程,他有若干個選擇項,可以控制他所等待的特定進程
- 實際上,wait函數是waitpid的一個特例
守護進程
- 創建子進程,然后父進程退出
- 在子進程中創建新的會話 setsid();
- 改變目錄為根目錄 chdir("/");
- 重設文件的權限掩碼umask(0);
- 關閉文件描述符 for(int i = 0;i < MAXFILE; i++){close(i);}
- 守護進程的工作 while(1){}
#include <iostream>
#include <unistd.h>
#include <ftw.h>
#include <fcntl.h>int main(){pid_t pc;char *buf = "This is a Daemon\n";int len = strlen(buf);pc = fork();if (pc < 0){printf("Error fork!\n");exit(-1);} else if (pc > 0){exit(0);}setsid();//在子進程中創建新的會話chdir("/");//改變目錄為根目錄umask(0);//重設文件的權限掩碼for (int i = 0; i < 65535; ++i) {close(i);}int fd;while (1){if (fd = open("/tmp/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600) < 0){perror("open");exit(1);}write(fd,buf,len+1);close(fd);sleep(10);}return 0;
}
sleep函數
- 函數使用sleep用來指定進程掛起來的指定的秒數。該函數的調用格式如下
- unsigned int sleep(unsigned int seconds);
- 頭文件 unistd.h
進程間通信
- 數據傳輸:一個進程將 數據 發送給另外一個進程,數據大小一般是一個字節到幾兆字節之間
- 共享數據:多個進程之間操作共享數據,一個進程對共享數據的修改,另外的進程可以看到
- 通知事件:一個進程向另外一個進程發送消息,通知它發生了某件事情。(如:子進程終止,通知父進程)
- 資源共享:多個進程之間共享相同的資源,需要內核提供 鎖 和 同步機制
- 進程控制:有些進程完全控制另一個進程的執行(如Debug),這個時候,進程會攔截另外一個進程所有的陷入(陷入內核)和異常,并且及時知道他的狀態改變
- 進程間通信 (IPC)
- Linux進程間通信 包括Unix進程間通信、Socket進程間通信、POSIX進程間通信、System V進程間通信
- Unix進程間通信 -> 管道 FIFO 信號
- System V進程間通信 ->?System V消息隊列??System V信號燈??System V共享內存
- POSIX進程間通信 -> posix消息隊列?posix信號燈??posix共享內存
現在的linux使用的進程間通信方式
- 管道(pipe) 和 有名管道(FIFO)
- 信號(signal):比較復雜的通信方式,用于通知其余進程,信號可以用于進程之間通信外,信號還可以發送信號給進程本身。
- 消息隊列:消息的鏈接表,包括posix消息隊列和system V消息隊列;有權限的進程可以讀寫消息隊列;克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區大小受限等缺陷
- 共享內存:多個進程訪問同一塊內存空間,是最快的IPC方式,往往結合信號量來實現進程間同步以及互斥
- 信號量 (使用不多)
- 套接字(socket)? (網絡)
管道和有名管道的區別
- 管道:具有親緣關系的進程間通信,需要通信雙方的進程有共同的祖先進程
- 有名管道:允許無親緣關系的管道之間通信,在文件系統中有對應的文件名字;有名管道通過命令 mkfifo或系統調用mkfifo來創建
具體使用
管道
- linux命令允許重定向,重定向就是管道 例如 ls > 1.txt
- 管道是單向的、先進先出的,無結構的、固定大小的字節流,他把一個進程的標準輸出和另外一個進程的標準輸入連接在一起
- 寫進程在管道的末尾輸入數據,讀進程在管道的首端讀取數據。數據被讀出后會從管道中移除,其他進程對此不可見了
- 管道提供了簡單的流控制機制。進程試圖讀取空的管道時,在有數據填充管道之前會處于阻塞狀態;同理,管道數據已滿時,未有讀進程讀取數據,寫進程往管道里面寫入數據會處于阻塞狀態
- 管道用于進程之間通信
- 使用系統調用pipe() 函數,創建一個簡單的管道,只需接受一個參數,也就是一個包括兩個整數的數組。
- 如果系統調用成功,此數組將包括管道使用的兩個文件描述符
- 創建一個管道之后,一般情況下進程將會產生一個新的進程
- 系統調用pip()? 原型:int pipe(int fd[2])
- 返回值:成功返回0 ;失敗返回 -1
- errno = EMFILE? 沒有空閑的文件描述符
- errno = ENFILE? 系統文件表已滿
- errno = EFAULT? fd數組無效
代碼
#include <stdio.h>
#include <unistd.h>int main(void)
{int pipe_fd[2];if(pipe(pipe_fd) < 0){printf("pipe create error!\n");return -1;} else{printf("pipe create success!\n");}close(pipe_fd[0]);close(pipe_fd[1]);return 0;
}
- 先創建一個管道 再創建一個子進程?
父子進程之間通信? 父進程寫入;子進程讀入數據
- 當讀到10的時候,結束進程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{int pipe_fd[2];pid_t pid;char buf[2]={0};//在創建子進程之前創建管道if(pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);} else{printf("pipe create success!\n");printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);}pid = fork();//創建子進程//子進程執行的代碼if (pid == 0){close(pipe_fd[1]);while (1){if (read(pipe_fd[0],buf,1) > 0){
// if (buf[0] % 3 == 0 && buf[0] != 0)printf("%d\n",buf[0]);if (buf[0] == 10)_exit(0);}}close(pipe_fd[0]);} else{//父進程執行的代碼close(pipe_fd[0]);for (buf[0]=0;buf[0]<=10;buf[0]++) {write(pipe_fd[1],buf,1);}close(pipe_fd[1]);wait(nullptr);}return 0;
}
上面這個例子 感覺有問題?
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <cstring>int main(){int pipe_fd[2];if (pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);return -1;} else{printf("pipe create success!\n");printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);}pid_t pid = fork();
// char buf[2] = {0};char buf[100] ={"Hello world"};int len = strlen(buf);if (pid == 0){close(pipe_fd[1]);while(1){if (read(pipe_fd[0],buf,len) > 0){printf("%s\n",buf);}}close(pipe_fd[0]);} else{//父進程執行此段代碼close(pipe_fd[0]);for (int i = 0;i<10;i++){write(pipe_fd[1], buf, len);}close(pipe_fd[1]);wait(nullptr);}return 0;
}
?簡單修改如下
?
- ?通過打開兩根管道,實現一個雙向的管道,但是需要在子進程中正確的設置文件的描述符號
- 必須在fork之前使用pipe(),否則子進程將不會繼承文件的描述符
- 使用半雙工管道的時候,任何關聯的進程都需要共享一個相關的祖先進程。因為管道存儲在系統內核中,不繼承祖先將無法尋址,有名管道就不會出現這個問題
代碼
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{int pipe_fd[2];int pipe_fd1[2];pid_t pid;char buf_r[100]={0};char * p_wbuf = nullptr;int r_num;//在創建子進程之前創建管道if(pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);}if(pipe(pipe_fd1) < 0){printf("pipe create error!\n");_exit(-1);}printf("pipe create success!\n");
// printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);
// printf("pipe_fd1[0] = %d,pipe_fd1[1] = %d",pipe_fd1[0],pipe_fd1[1]);pid = fork();//創建子進程//子進程執行的代碼if (pid == 0){printf("******子進程******\n");//read pipeclose(pipe_fd[1]);r_num = read(pipe_fd[0],buf_r,100);if (r_num > 0){printf("children r_num = %d, pipe is: %s\n",r_num,buf_r);}close(pipe_fd[0]);//write pipeclose(pipe_fd1[0]);if (write(pipe_fd1[1],"ByeBye!",7)!=-1){printf("children process write ByeBye! success!\n");}close(pipe_fd1[1]);printf("children process exit!\n");_exit(0);} else if (pid > 0){printf("******父進程******\n");//write pipeclose(pipe_fd[0]);if (write(pipe_fd[1],"Hello!",6) !=-1){printf("father process write Hello! success!\n");}close(pipe_fd[1]);//read processclose(pipe_fd1[1]);r_num = read(pipe_fd1[0],buf_r,100);if (r_num > 0){printf("father r_num = %d, pipe is: %s\n",r_num,buf_r);}close(pipe_fd1[0]);wait(nullptr);printf("father process exit!\n");_exit(0);}return 0;
}
- ?父進程 給 子進程 傳輸 Hello!;接收輸出子進程的 ByeBye!
- 子進程 給 父進程 傳輸?ByeBye!;接收輸出父進程的 Hello!
- 子進程退出,然后父進程退出
- r_num 為讀取的數據長度
命名管道
- 作為一個特殊的設備文件而存在
- 適用于無血緣關系的進程之間通信
- 即使進程之間不再需要管道,但是命名管道仍然繼續保存在文件系統中,便于以后使用
- 使用函數 int mkfifo(const char * pathname,mode_t mode)
- 函數說明:mkfifo()會依參數pathname 建立特殊的FIFO 文件, 該文件必須不存在, 而參數mode 為該文件的權限 (mode%~umask), 因此umask 值也會影響到FIFO 文件的權限. Mkfifo()建立的FIFO 文件其他進程都可以用讀寫一般文件的方式存取. 當使用open()來打開FIFO 文件時, O_NONBLOCK 旗標會有影響:
- 頭文件:#include <sys/types.h> ??#include <sys/stat.h>
- 1. 當使用O_NONBLOCK 旗標時, 打開FIFO 文件來讀取的操作會立刻返回, 但是若還沒有其他進程打開FIFO 文件來讀取, 則寫入的操作會返回ENXIO 錯誤代碼.
- 2. 沒有使用O_NONBLOCK 旗標時, 打開FIFO 來讀取的操作會等到其他進程打開FIFO 文件來寫入才正常返回. 同樣地, 打開FIFO 文件來寫入的操作會等到其他進程打開FIFO 文件來讀取后才正常返回.
- 返回值:若成功則返回0, 否則返回-1, 錯誤原因存于errno 中
- 使用ls -l查看管道文件 顯示p開頭
- 普通文件 是一個 -?
錯誤代碼:
- 1、EACCESS 參數pathname 所指定的目錄路徑無可執行的權限
- 2、EEXIST 參數pathname 所指定的文件已存在.
- 3、ENAMETOOLONG 參數pathname 的路徑名稱太長.
- 4、ENOENT 參數pathname 包含的目錄不存在
- 5、ENOSPC 文件系統的剩余空間不足
- 6、ENOTDIR 參數pathname 路徑中的目錄存在但卻非真正的目錄.
- 7、EROFS 參數pathname 指定的文件存在于只讀文件系統內.
注意事項
- 打開FIFO,使用非阻塞標志(O_NONBLOCK)產生以下的影響
- 如果未說明O_NONBLOCK,只讀打開會要阻塞某個寫進程打開此FIFO;類似,只寫打開要阻塞某個讀進程打開此FIFO;讀寫互斥
- 如果指定了O_NONBLOCK,則只讀打開立即返回,但是如果沒有進程為了讀而打開一個FIFO,那么只寫打開將出錯返回,其errno是ENXIO
- 類似管道,若寫一個尚無進程為讀而打開的FIFO,則產生信號為SIGPIPE;若某個FIFO最后一個寫進程關閉了該FIFO,則將為該FIFO的讀進程產生一個文件結束標志
參考鏈接
- Linux進程描述符task_struct結構體詳解--Linux進程的管理與調度(一)
- Linux 系統啟動過程
- Linux系統啟動流程詳解
- fork和exec的區別
- Linux中fork,vfork和clone詳解(區別與聯系)
- C語言vfork()函數:建立新的進程
- linux系統編程之進程(五):exec系列函數(execl,execlp,execle,execv,execvp)使用
- C語言waitpid()函數:中斷(結束)進程函數(等待子進程中斷或
- linux守護進程詳解及創建,daemon()使用
- linux系統的7種運行級別
- SGX的內部組件概述(一)找一個好的工作,為自己活
- C語言mkfifo()函數:建立具名管道
- linux驅動重要頭文件!!!
- errno頭文件