嵌入式Linux多任务编程 进程 管道 命名管道
生活随笔
收集整理的這篇文章主要介紹了
嵌入式Linux多任务编程 进程 管道 命名管道
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
進程
- 進程是一個可并發(fā)執(zhí)行的具有獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次執(zhí)行過程,也是操作系統(tǒng)執(zhí)行資源分配和保護的基本單位。
- 程序的一次執(zhí)行就是一個進程
- 一個程序可以派生多個進程
- 多個不同程序運行的時候,也會有多個相對應(yīng)的進程與其相互對應(yīng)
- 進程是動態(tài)的,有始有終,有自己的生命周期,有進程狀態(tài)的變化
程序與進程的區(qū)別
- 程序是靜止的,無生命的,進程是活動的
- 程序是可以脫離機器長期存在,而進程是執(zhí)行了的程序
- 程序不具備并發(fā)性,不占據(jù)系統(tǒng)的資源,進程則相反,具備并發(fā)性、會占據(jù)內(nèi)存空間,并且會受到其他進程的制約和影響
- 一個程序?qū)?yīng)很多的進程
進程的狀態(tài)
- 運行狀態(tài)
- 就緒狀態(tài) 缺少cpu
- 等待狀態(tài) 缺少IO資源,等待
相關(guān)命令
- 使用命令 ps -ax 查看進程的相關(guān)狀態(tài);比如進程號 、 進程的狀態(tài)、
進程樹的形成
- 計算機啟動之后,BIOS從磁盤引導(dǎo)扇區(qū)加載系統(tǒng)引導(dǎo)程序,它將Linux系統(tǒng)裝入內(nèi)存,并且跳到內(nèi)核處開始執(zhí)行,Linux內(nèi)核就執(zhí)行初始化工作:初始化硬件、初始化內(nèi)部數(shù)據(jù)結(jié)構(gòu)、建立進程0
- 進程0創(chuàng)建進程1,進程1是以后所有創(chuàng)建進程的祖先,它負責(zé)初始化所有的用戶進程。進程1創(chuàng)建shell,shell進程顯示提示符,等待命令的輸入
- init進程ID為1,通常是init進程,在自檢過程結(jié)束的時候由內(nèi)核調(diào)用
- init進程不會終止
- init進程是一個普通的用戶進程(與交換進程不同,他不是內(nèi)核中的系統(tǒng)進程,但是它以超級用戶特權(quán)執(zhí)行)
進程的創(chuàng)建
- 任何進程的創(chuàng)建,都是基于現(xiàn)有的進程
- 進程的創(chuàng)建可以使用fork 和 exec
- fork 視為新的進程分配響應(yīng)的數(shù)據(jù)結(jié)構(gòu),并且將父進程的相應(yīng)上下文信息復(fù)制過來
- exec 將可執(zhí)行文件的正文和數(shù)據(jù)轉(zhuǎn)入內(nèi)存替代先前的內(nèi)容(從父進程復(fù)制過來的數(shù)據(jù)),并開始執(zhí)行正文段,將當前進程替換為一個新的進程
進程的終止
- 使用exit()
- exit釋放除了task_struct以外的所有的上下文,父進程收到子進程的終結(jié)信號之后,釋放子進程的task_struct
- vfork調(diào)用之后,將立即調(diào)用exec,這樣就不需要拷貝父進程的所有的頁表,因此比fork快
- pid_t fork(void);
- pid_t vfor(void);
- 頭文件 unistd.h
fork系統(tǒng)調(diào)用
- 當fork函數(shù)調(diào)用成功時,對父進程返回子進程的PID,對子進程返回0
- 調(diào)用失敗的時候,父進程返回-1,并沒有子進程的創(chuàng)建
- fork函數(shù)返回 兩個參數(shù),分別給父進程和子進程
exec系統(tǒng)調(diào)用
- exec是用來執(zhí)行一個可執(zhí)行文件來代替當前進程的執(zhí)行鏡像
- 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[])
函數(shù)的原型
- int execve(const char * filename,char * const argv[],char * const envp[])
- filename? ?執(zhí)行的文件
- argv? ? ? ? ? 傳遞給文件的參數(shù)
- envp? ? ? ? ?傳遞給文件的環(huán)境變量
- 當參數(shù)path所指定的文件替換原進程的執(zhí)行鏡像之后,文件的path開始執(zhí)行,參數(shù)argv和envp便傳遞給進程
- l 表示list
- v表示vector
- e 可以傳遞新進程環(huán)境變量 execle execve
- p 可執(zhí)行文件查找方式為文件的名字 execlp execvp
函數(shù)
- ?頭文件 <sys/types.h> <unistd.h>
- pid_t getpid(void);返回調(diào)用進程的進程ID
- pid_t getppid(void);但會調(diào)用進程的父進程ID
- uid_t getuid(void);返回調(diào)用進程的實際用戶ID
- uid_t geteuid(void);返回進程的有效用戶ID
- gid_t getgid(void);返回進程的實際組ID
- gid_t getegit(void);調(diào)用進程的有效組ID
?exit系統(tǒng)調(diào)用
- _exit 直接結(jié)束進程,清除其內(nèi)存使用空間,并且清除其在內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)
- exit 函數(shù)在調(diào)用之前會檢查文件的打開情況,把文件的緩存區(qū)的內(nèi)容寫會文件中,比如調(diào)用printf()函數(shù)
wait系統(tǒng)調(diào)用
- wait函數(shù) 用于使父進程阻塞,直到一個進程結(jié)束或者該進程收到一個指定信號為止
- 調(diào)用wait或者waitpid的進程可能會:
- 阻塞:如果其所有的進程還在運行
- 帶子進程的終止狀態(tài)立即返回(如果一個子進程已經(jīng)終止,正等待父進程存取其終止狀態(tài))
- 出錯立即返回(如果沒有任何子進程)
- pid_t wait(int *status);? status返回子進程退出時的狀態(tài)信息
- pid_t waitpid(pid_t pid,int *status,int options);//等待指定的進程id
- 頭文件 <sys/types.h> 和 <sys/wait.h>
- 兩個函數(shù)返回數(shù)值:如果成功返回子進程的ID號,如果出現(xiàn)問題,返回-1
wait和waitpid函數(shù)的區(qū)別
- 在一個子進程終止之前,wait使其調(diào)用者阻塞,而waitpid有一個選項,可以不阻塞調(diào)用者
- waitpid并不等待第一個終止的子進程,他有若干個選擇項,可以控制他所等待的特定進程
- 實際上,wait函數(shù)是waitpid的一個特例
守護進程
- 創(chuàng)建子進程,然后父進程退出
- 在子進程中創(chuàng)建新的會話 setsid();
- 改變目錄為根目錄 chdir("/");
- 重設(shè)文件的權(quán)限掩碼umask(0);
- 關(guān)閉文件描述符 for(int i = 0;i < MAXFILE; i++){close(i);}
- 守護進程的工作 while(1){}
sleep函數(shù)
- 函數(shù)使用sleep用來指定進程掛起來的指定的秒數(shù)。該函數(shù)的調(diào)用格式如下
- unsigned int sleep(unsigned int seconds);
- 頭文件 unistd.h
進程間通信
- 數(shù)據(jù)傳輸:一個進程將 數(shù)據(jù) 發(fā)送給另外一個進程,數(shù)據(jù)大小一般是一個字節(jié)到幾兆字節(jié)之間
- 共享數(shù)據(jù):多個進程之間操作共享數(shù)據(jù),一個進程對共享數(shù)據(jù)的修改,另外的進程可以看到
- 通知事件:一個進程向另外一個進程發(fā)送消息,通知它發(fā)生了某件事情。(如:子進程終止,通知父進程)
- 資源共享:多個進程之間共享相同的資源,需要內(nèi)核提供 鎖 和 同步機制
- 進程控制:有些進程完全控制另一個進程的執(zhí)行(如Debug),這個時候,進程會攔截另外一個進程所有的陷入(陷入內(nèi)核)和異常,并且及時知道他的狀態(tài)改變
- 進程間通信 (IPC)
- Linux進程間通信 包括Unix進程間通信、Socket進程間通信、POSIX進程間通信、System V進程間通信
- Unix進程間通信 -> 管道 FIFO 信號
- System V進程間通信 ->?System V消息隊列??System V信號燈??System V共享內(nèi)存
- POSIX進程間通信 -> posix消息隊列?posix信號燈??posix共享內(nèi)存
現(xiàn)在的linux使用的進程間通信方式
- 管道(pipe) 和 有名管道(FIFO)
- 信號(signal):比較復(fù)雜的通信方式,用于通知其余進程,信號可以用于進程之間通信外,信號還可以發(fā)送信號給進程本身。
- 消息隊列:消息的鏈接表,包括posix消息隊列和system V消息隊列;有權(quán)限的進程可以讀寫消息隊列;克服了信號承載信息量少,管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺陷
- 共享內(nèi)存:多個進程訪問同一塊內(nèi)存空間,是最快的IPC方式,往往結(jié)合信號量來實現(xiàn)進程間同步以及互斥
- 信號量 (使用不多)
- 套接字(socket)? (網(wǎng)絡(luò))
管道和有名管道的區(qū)別
- 管道:具有親緣關(guān)系的進程間通信,需要通信雙方的進程有共同的祖先進程
- 有名管道:允許無親緣關(guān)系的管道之間通信,在文件系統(tǒng)中有對應(yīng)的文件名字;有名管道通過命令 mkfifo或系統(tǒng)調(diào)用mkfifo來創(chuàng)建
具體使用
管道
- linux命令允許重定向,重定向就是管道 例如 ls > 1.txt
- 管道是單向的、先進先出的,無結(jié)構(gòu)的、固定大小的字節(jié)流,他把一個進程的標準輸出和另外一個進程的標準輸入連接在一起
- 寫進程在管道的末尾輸入數(shù)據(jù),讀進程在管道的首端讀取數(shù)據(jù)。數(shù)據(jù)被讀出后會從管道中移除,其他進程對此不可見了
- 管道提供了簡單的流控制機制。進程試圖讀取空的管道時,在有數(shù)據(jù)填充管道之前會處于阻塞狀態(tài);同理,管道數(shù)據(jù)已滿時,未有讀進程讀取數(shù)據(jù),寫進程往管道里面寫入數(shù)據(jù)會處于阻塞狀態(tài)
- 管道用于進程之間通信
- 使用系統(tǒng)調(diào)用pipe() 函數(shù),創(chuàng)建一個簡單的管道,只需接受一個參數(shù),也就是一個包括兩個整數(shù)的數(shù)組。
- 如果系統(tǒng)調(diào)用成功,此數(shù)組將包括管道使用的兩個文件描述符
- 創(chuàng)建一個管道之后,一般情況下進程將會產(chǎn)生一個新的進程
- 系統(tǒng)調(diào)用pip()? 原型:int pipe(int fd[2])
- 返回值:成功返回0 ;失敗返回 -1
- errno = EMFILE? 沒有空閑的文件描述符
- errno = ENFILE? 系統(tǒng)文件表已滿
- errno = EFAULT? fd數(shù)組無效
代碼
#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; }- 先創(chuàng)建一個管道 再創(chuàng)建一個子進程?
父子進程之間通信? 父進程寫入;子進程讀入數(shù)據(jù)
- 當讀到10的時候,結(jié)束進程
上面這個例子 感覺有問題?
#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{//父進程執(zhí)行此段代碼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; }?簡單修改如下
?
- ?通過打開兩根管道,實現(xiàn)一個雙向的管道,但是需要在子進程中正確的設(shè)置文件的描述符號
- 必須在fork之前使用pipe(),否則子進程將不會繼承文件的描述符
- 使用半雙工管道的時候,任何關(guān)聯(lián)的進程都需要共享一個相關(guān)的祖先進程。因為管道存儲在系統(tǒng)內(nèi)核中,不繼承祖先將無法尋址,有名管道就不會出現(xiàn)這個問題
代碼
#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;//在創(chuàng)建子進程之前創(chuàng)建管道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();//創(chuàng)建子進程//子進程執(zhí)行的代碼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 為讀取的數(shù)據(jù)長度
命名管道
- 作為一個特殊的設(shè)備文件而存在
- 適用于無血緣關(guān)系的進程之間通信
- 即使進程之間不再需要管道,但是命名管道仍然繼續(xù)保存在文件系統(tǒng)中,便于以后使用
- 使用函數(shù) int mkfifo(const char * pathname,mode_t mode)
- 函數(shù)說明:mkfifo()會依參數(shù)pathname 建立特殊的FIFO 文件, 該文件必須不存在, 而參數(shù)mode 為該文件的權(quán)限 (mode%~umask), 因此umask 值也會影響到FIFO 文件的權(quán)限. 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 參數(shù)pathname 所指定的目錄路徑無可執(zhí)行的權(quán)限
- 2、EEXIST 參數(shù)pathname 所指定的文件已存在.
- 3、ENAMETOOLONG 參數(shù)pathname 的路徑名稱太長.
- 4、ENOENT 參數(shù)pathname 包含的目錄不存在
- 5、ENOSPC 文件系統(tǒng)的剩余空間不足
- 6、ENOTDIR 參數(shù)pathname 路徑中的目錄存在但卻非真正的目錄.
- 7、EROFS 參數(shù)pathname 指定的文件存在于只讀文件系統(tǒng)內(nèi).
注意事項
- 打開FIFO,使用非阻塞標志(O_NONBLOCK)產(chǎn)生以下的影響
- 如果未說明O_NONBLOCK,只讀打開會要阻塞某個寫進程打開此FIFO;類似,只寫打開要阻塞某個讀進程打開此FIFO;讀寫互斥
- 如果指定了O_NONBLOCK,則只讀打開立即返回,但是如果沒有進程為了讀而打開一個FIFO,那么只寫打開將出錯返回,其errno是ENXIO
- 類似管道,若寫一個尚無進程為讀而打開的FIFO,則產(chǎn)生信號為SIGPIPE;若某個FIFO最后一個寫進程關(guān)閉了該FIFO,則將為該FIFO的讀進程產(chǎn)生一個文件結(jié)束標志
參考鏈接
- Linux進程描述符task_struct結(jié)構(gòu)體詳解--Linux進程的管理與調(diào)度(一)
- Linux 系統(tǒng)啟動過程
- Linux系統(tǒng)啟動流程詳解
- fork和exec的區(qū)別
- Linux中fork,vfork和clone詳解(區(qū)別與聯(lián)系)
- C語言vfork()函數(shù):建立新的進程
- linux系統(tǒng)編程之進程(五):exec系列函數(shù)(execl,execlp,execle,execv,execvp)使用
- C語言waitpid()函數(shù):中斷(結(jié)束)進程函數(shù)(等待子進程中斷或
- linux守護進程詳解及創(chuàng)建,daemon()使用
- linux系統(tǒng)的7種運行級別
- SGX的內(nèi)部組件概述(一)找一個好的工作,為自己活
- C語言mkfifo()函數(shù):建立具名管道
- linux驅(qū)動重要頭文件!!!
- errno頭文件
總結(jié)
以上是生活随笔為你收集整理的嵌入式Linux多任务编程 进程 管道 命名管道的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人民币鉴别真伪的方法 怎么分辨人民币真假
- 下一篇: Linux环境下实现unsigned c