什么是進程
1、進程和線程的區別
進程是指正在運行的程序,它擁有獨立的內存空間和系統資源,不同進程之間的數據不共享。進程是資源分配的基本單位。
線程是進程內的執行單元,它與同一進程內的其他線程共享進程的內存空間和系統資源。線程是調度的基本單位。
2、進程的創建和銷毀
在Linux中啟動一個進程有多種方法:
(1)通過system函數啟動進程。(使用簡單,效率較低)
?
#include? /** ?*?@brief?執行系統命令調用命令處理器來執行命令 ?* ?*?Detailed?function?description ?* ?*?@param[in]?command:?包含被請求變量名稱的?C?字符串 ?* ?*?@return?如果發生錯誤,則返回值為?-1,否則返回命令的狀態。 ?*/ int?system(const?char?*command);
?
例子:通過system函數啟動一個進程,列出當前目錄下的文件及文件夾。
?
#include? #include? int?main(void) { ????system("ls"); ????printf("ls?end "); ????return?0; }
?
(2)通過fork函數啟動進程。(用于啟動子進程)
?
#include? #include? /** ?*?@brief?fork系統調用用于創建一個子進程 ?* ?*?Detailed?function?description ?* ?*?@param[in] ?* ?*?@return?如果發生錯誤,則返回值為?-1,否則返回命令的狀態。 ?*/ pid_t?fork(void);
?
例子:通過fork函數啟動子進程
?
#include? #include? #include? #include? int?main(void)? { ????pid_t?res?=?fork(); ????///?0)? ????{ ????????printf("res?=?%d,?I?am?parent?process.?pid?=?%d ",?res,?getpid()); ????????int?child_status?=?0; ????????pid_t?child_pid?=?wait(&child_status);???///?
編譯、運行:
我們使用了fork()系統調用來創建一個新進程。如果fork()返回值為0,則說明當前進程是子進程;如果返回值大于0,則說明當前進程是父進程。在父進程中,我們使用wait()系統調用來等待子進程結束。當子進程結束后,父進程會繼續執行。
(3)通過exec系列函數啟動進程。(用于啟動新進程,新進程會覆蓋舊進程)
?
#include? /** ?*?@brief?啟動新進程,新進程會覆蓋舊進程 ?* ?*?Detailed?function?description ?* ?*?@param[in]?path:?所執行文件的路徑 ?*?@param[in]?file:?所執行文件的名稱 ?*?@param[in]?arg:?傳入的參數列表,以NULL作為結束 ?*?@param[in]?envp:?傳入的環境變量 ?* ?*?@return?如果發生錯誤,則返回值為?-1,否則返回命令的狀態。 ?*/ int?execl(const?char?*path,?const?char?*arg,?...); int?execlp(const?char?*file,?const?char?*arg,?...); int?execle(const?char?*path,?const?char?*arg,?...,?char?*const?envp[]); int?execv(const?char?*path,?char?*const?argv[]); int?execvp(const?char?*file,?char?*const?argv[]); int?execve(const?char?*path,?char?*const?argv[],?char?*const?envp[]);?
例子:通過execl()函數的參數列表調用了ls命令程序
?
#include? #include? int?main(void) { ????execl("/bin/ls",?"ls",?"-la",?NULL); ????printf("ls?end "); ????return?0; }?
execl()函數的參數列表調用了ls命令程序,與在終端上運行”ls -la”產生的結果是一樣的。
在Linux中終止一個進程有多種方法:
從main函數返回。(正常終止)
調用exit()函數終止。(正常終止)
調用_exit()函數終止。(正常終止)
調用abort()函數終止。(異常終止)
由系統信號終止。(異常終止)
進程間通信方式
進程間通信是指在不同進程之間傳播或交換信息的一種機制。每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程A把數據從用戶空間拷到內核緩沖區,進程B再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信。
進程間通信的目的:
傳輸數據。比如進程 A 負責生成數據,進程 B 負責處理數據,數據需要從 A 進程傳輸至 B 進程。
共享資源。比如進程 A 與進程 B 共享某一塊內存資源。
模塊化。將系統功能劃分為多個進程模塊進行開發,方便開發維護。
加速計算。多核處理器環境,一個特定進程劃分為幾個進程并行運行。
Linux IPC(Inter-process Comminication, 進程間通信)的方式:
1、消息隊列
內核中的一個優先級隊列,多個進程通過訪問同一個隊列,進行添加結點或者獲取結點實現通信。
POSIX消息隊列頭文件:
?
#include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include??
編譯鏈接需要加上 -lrt 鏈接。
消息隊列API接口:
?
/** ?*?@brief?創建消息隊列實例 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?消息隊列名稱 ?*?@param[in]?oflag:根據傳入標識來創建或者打開一個已創建的消息隊列 ????????????????????-?O_CREAT:?創建一個消息隊列 ????????????????????-?O_EXCL:?檢查消息隊列是否存在,一般與O_CREAT一起使用 ????????????????????-?O_CREAT|O_EXCL:?消息隊列不存在則創建,已存在返回NULL ????????????????????-?O_NONBLOCK:?非阻塞模式打開,消息隊列不存在返回NULL ????????????????????-?O_RDONLY:?只讀模式打開 ????????????????????-?O_WRONLY:?只寫模式打開 ????????????????????-?O_RDWR:?讀寫模式打開 ?*?@param[in]?mode:訪問權限 ?*?@param[in]?attr:消息隊列屬性地址 ?* ?*?@return?成功返回消息隊列描述符,失敗返回-1,錯誤碼存于error中 ?*/ mqd_t?mq_open(const?char?*name,?int?oflag,??mode_t?mode,?struct?mq_attr?*attr); /** ?*?@brief?無限阻塞方式接收消息 ?* ?*?Detailed?function?description ?* ?*?@param[in]?mqdes:?消息隊列描述符 ?*?@param[in]?msg_ptr:消息體緩沖區地址 ?*?@param[in]?msg_len:消息體長度,長度必須大于等于消息屬性設定的最大值 ?*?@param[in]?msg_prio:消息優先級 ?* ?*?@return?成功返回消息長度,失敗返回-1,錯誤碼存于error中 ?*/ mqd_t?mq_receive(mqd_t?mqdes,?char?*msg_ptr,?size_t?msg_len,?unsigned?*msg_prio); /** ?*?@brief?指定超時時間阻塞方式接收消息 ?* ?*?Detailed?function?description ?* ?*?@param[in]?mqdes:?消息隊列描述符 ?*?@param[in]?msg_ptr:消息體緩沖區地址 ?*?@param[in]?msg_len:消息體長度,長度必須大于等于消息屬性設定的最大值 ?*?@param[in]?msg_prio:消息優先級 ?*?@param[in]?abs_timeout:超時時間 ?* ?*?@return?成功返回消息長度,失敗返回-1,錯誤碼存于error中 ?*/ mqd_t?mq_timedreceive(mqd_t?mqdes,?char?*msg_ptr,?size_t?msg_len,?unsigned?*msg_prio,?const?struct?timespec?*abs_timeout); /** ?*?@brief?無限阻塞方式發送消息 ?* ?*?Detailed?function?description ?* ?*?@param[in]?mqdes:?消息隊列描述符 ?*?@param[in]?msg_ptr:待發送消息體緩沖區地址 ?*?@param[in]?msg_len:消息體長度 ?*?@param[in]?msg_prio:消息優先級 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ mqd_t?mq_send(mqd_t?mqdes,?const?char?*msg_ptr,?size_t?msg_len,?unsigned?msg_prio); /** ?*?@brief?指定超時時間阻塞方式發送消息 ?* ?*?Detailed?function?description ?* ?*?@param[in]?mqdes:?消息隊列描述符 ?*?@param[in]?msg_ptr:待發送消息體緩沖區地址 ?*?@param[in]?msg_len:消息體長度 ?*?@param[in]?msg_prio:消息優先級 ?*?@param[in]?abs_timeout:超時時間 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ mqd_t?mq_timedsend(mqd_t?mqdes,?const?char?*msg_ptr,?size_t?msg_len,?unsigned?msg_prio,?const?struct?timespec?*abs_timeout); /** ?*?@brief?關閉消息隊列 ?* ?*?Detailed?function?description ?* ?*?@param[in]?mqdes:?消息隊列描述符 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ mqd_t?mq_close(mqd_t?mqdes); /** ?*?@brief?分離消息隊列 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?消息隊列名稱 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ mqd_t?mq_unlink(const?char?*name);?
消息隊列基本API接口使用例子:發送進程給接收進程發送測試數據。
send.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #define?MQ_MSG_MAX_SIZE????512??///?
recv.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #define?MQ_MSG_MAX_SIZE????512??///?
編譯、運行:
?
gcc?send.c?-o?send_process?-lrt gcc?recv.c?-o?recv_process?-lrt?
2、共享內存
消息隊列的讀取和寫入的過程,會有發生用戶態與內核態之間的消息拷貝過程。而共享內存的方式則沒有這個拷貝過程,進程間通信速度較快。
在物理內存上開辟一塊內存空間,多個進程可以將同一塊物理內存空間映射到自己的虛擬地址空間,通過自己的虛擬地址直接訪問這塊空間,通過這種方式實現數據共享。
POSIX共享內存頭文件:
?
#include? #include? #include??
共享內存API接口:
?
/** ?*?@brief?創建共享內存實例 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?要打開或創建的共享內存文件名 ?*?@param[in]?oflag:打開的文件操作屬性 ????????????????????-?O_CREAT:?創建一個共享內存文件 ????????????????????-?O_EXCL:?檢查共享內存是否存在,一般與O_CREAT一起使用 ????????????????????-?O_CREAT|O_EXCL:?共享內存不存在則創建,已存在返回NULL ????????????????????-?O_NONBLOCK:?非阻塞模式打開,共享內存不存在返回NULL ????????????????????-?O_RDONLY:?只讀模式打開 ????????????????????-?O_WRONLY:?只寫模式打開 ????????????????????-?O_RDWR:?讀寫模式打開 ?*?@param[in]?mode:文件共享模式,例如?0777 ?* ?*?@return?成功返回共享內存描述符,失敗返回-1,錯誤碼存于error中 ?*/ int?shm_open(const?char?*name,?int?oflag,?mode_t?mode); /** ?*?@brief?刪除共享內存 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?創建的共享內存文件名 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?shm_unlink(const?char?*name); /** ?*?@brief?將打開的文件映射到內存 ?* ?*?Detailed?function?description ?* ?*?@param[in]?addr:?要將文件映射到的內存地址,一般應該傳遞NULL來由Linux內核指定 ?*?@param[in]?length:?要映射的文件數據長度 ?*?@param[in]?prot:?映射的內存區域的操作權限(保護屬性),包括PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE ?*?@param[in]?flags:?標志位參數,包括:MAP_SHARED、MAP_PRIVATE與MAP_ANONYMOUS。 ?*?@param[in]?fd:??用來建立映射區的文件描述符,用?shm_open打開或者open打開的文件 ?*?@param[in]?offset:?映射文件相對于文件頭的偏移位置,應該按4096字節對齊 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ void?*mmap(void?*addr,?size_t?length,?int?prot,?int?flags,?int?fd,?off_t?offset); ? /** ?*?@brief?取消內存映射 ?* ?*?Detailed?function?description ?* ?*?@param[in]?addr:?由mmap成功返回的地址 ?*?@param[in]?length:?要取消的內存長度 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?munmap(void?*addr,?size_t?length); /** ?*?@brief?將參數fd指定的文件大小改為參數length指定的大小 ?* ?*?Detailed?function?description ?* ?*?@param[in]?fd:?已打開的文件描述符,以寫入模式打開的文件 ?*?@param[in]?length:?要設置的長度 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?ftruncate(int?fd,off_t?length); /** ?*?@brief?獲取文件相關的信息,將獲取到的信息放入到statbuf結構體中 ?* ?*?Detailed?function?description ?* ?*?@param[in]?fd:?已打開的文件描述符 ?*?@param[out]?statbuf:?文件的信息 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?fstat(int?fd,?struct?stat?*statbuf);?
共享內存基本API接口使用例子:發送進程給接收進程發送測試數據。
send.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #define?SHM_NAME?"/shm" int?main(void) { ????int?ret?=?0; ????///?
recv.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #define?SHM_NAME?"/shm" int?main(void) { ????///?
編譯、運行:
?
gcc?send.c?-o?send_process?-lrt gcc?recv.c?-o?recv_process?-lrt?
對具有多個處理核系統消息傳遞的性能要優于共享內存。共享內存會有高速緩存一致性問題,這是由共享數據在多個高速緩存之間遷移而引起的。隨著系統的處理核的數量的日益增加,可能導致消息傳遞作為 IPC 的首選機制。
3、socket
UNIX域套接字與傳統基于TCP/IP協議棧的socket不同,unix domain socket以文件系統作為地址空間,不需經過TCP/IP的頭部封裝、報文ack確認、路由選擇、數據校驗與重傳過程,因此傳輸速率上也不會受網卡帶寬的限制。
unix domain socket在進程間通信同樣是基于“客戶端—服務器”(C-S)模式。
UNIX域套接字基本API接口使用例子:基于UNIX域套接字客戶端進程向服務端進程發送測試數據。
server.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #include? #include? #include? #define?SERVER_PATH?"/tmp/server"? int?main(void) { ?///?
client.c:
?
#include? #include? #include? #include? #include????????????/*?For?O_*?constants?*/ #include?????????/*?For?mode?constants?*/ #include? #include? #include? #include? #define?SERVER_PATH?"/tmp/server" #define?CLIENT_PATH?"/tmp/client" int?main(void) { ?///?
編譯、運行:
?
gcc?server.c?-o?server_process gcc?client.c?-o?client_process?
類socket的其它進程間通信方式:
實用 | nanomsg通信庫的簡單使用分享
mqtt應用于進程間通信
4、管道
在內核中開辟一塊緩沖區;若多個進程拿到同一個管道(緩沖區)的操作句柄,就可以訪問同一個緩沖區,就可以進行通信。涉及到兩次用戶態與內核態之間的數據拷貝。
(1)匿名管道
內核中的緩沖區是沒有具體的標識符的,匿名管道只能用于具有親緣關系的進程間通信。
調用pipe接口可以創建一個匿名管道,并返回了兩個描述符,一個是管道的讀取端描述符 fd[0],另一個是管道的寫入端描述符 fd[1]。
管道是一個半雙工通信(可以選擇方向的單向傳輸)
匿名管道基本API接口使用例子:父進程通過管道發送測試數據給子進程。
?
#include? #include? #include? #include? int?main() { ????///?0) ???{ ??///?
編譯、運行:
如果需要雙向通信,則應該創建兩個管道。
(2)命名管道
命名管道也是內核中的一塊緩沖區,并且這個緩沖區具有標識符;這個標識符是一個可見于文件系統的管道文件,能夠被其他進程找到并打開管道文件,則可以獲取管道的操作句柄,所以該命名管道可用于同一主機上的任意進程間通信。
創建命名管道的接口:
?
int?mkfifo(const?char?*pathname,?mode_t?mode);?
命名管道基本API接口使用例子:一個進程往管道中寫入測試數據,另一個進程從管道中讀取數據。
fifo_wr.c:
?
#include? #include? #include? #include? #include? #include? #include? #define?FIFO_PATH??"./fifo_file" typedef?struct?_msg_data { ????char?buf[128]; ????int?cnt; }msg_data_t; void?send_data(int?fd) { ????static?int?cnt?=?0; ????msg_data_t?send_data?=?{0}; ????cnt++; ????strcpy(send_data.buf,?"hello"); ????send_data.cnt?=?cnt; ????write(fd,?&send_data,?sizeof(send_data)); ????printf("send?msg?=?%s,?cnt?=?%d ",?send_data.buf,?send_data.cnt); } int?main(void) { ????///?
fifo_rd.c:
?
#include? #include? #include? #include? #include? #include? #include? #define?FIFO_PATH??"./fifo_file" typedef?struct?_msg_data { ????char?buf[128]; ????int?cnt; }msg_data_t; int?main(void) { ????umask(0); ????///?
編譯、運行:
?
gcc?fifo_wr.c?-o?fifo_wr gcc?fifo_rd.c?-o?fifo_rd?
5、信號量
信號量(Seamphore)是進程和線程間同步的一種機制。
信號量本質是一個非負的整型變量。增加一個可用資源執行加一,也稱為V操作;獲取一個資源資源后執行減一,也稱為P操作。
信號量根據信號值不同可分為兩類:
二值信號量,信號量值只有0和1,初始值為1,1表示資源可用,0表示資源不可用;二值信號量與互斥鎖類似。
計數信號量, 信號量的值在0到一個大于1的限制值之間,信號值表示可用的資源的數目。
信號量根據作用對象不同可分為兩類:
有名信號量,信號值保存在文件中,用于進程間同步
無名信號量,又稱為基于內存信號量,信號值保存在內存中,用于線程間同步
POSIX信號量頭文件:
?
#include??
編譯鏈接需要加-lpthread參數。
信號量API接口:
?
/** ?*?@brief?創建信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?信號量名稱 ?*?@param[in]?mode:?訪問權限 ?*?@param[in]?value:?信號量初始值 ?* ?*?@return?成功時返回指向信號量的指針,出錯時為SEM_FAILED ?*/ sem_t?*sem_open(const?char?*name,int?oflag,?mode_t?mode,?unsigned?int?value); /** ?*?@brief?初始化信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?*?@param[in]?pshared:?信號量作用域,分為進程內作用域PTHREAD_PROCESS_PRIVATE和跨進程作用域PTHREAD_PROCESS_SHARED ?*?@param[in]?value:?信號量初始值 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_init(sem_t?*sem,?int?pshared,?unsigned?int?value); /** ?*?@brief?獲取信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?*?@param[out]?sval:?保存返回信號值地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_getvalue(sem_t?*sem,?int?*sval); /** ?*?@brief?阻塞方式等待信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_wait(sem_t?*sem); /** ?*?@brief?指定超時時間阻塞方式等待信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?*?@param[in]?sem:?超時時間,單位為時鐘節拍 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_timedwait(sem_t?*sem,?const?struct?timespec?*abs_timeout); /** ?*?@brief?非阻塞方式等待信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_trywait(sem_t?*sem); /** ?*?@brief?產生信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_post(sem_t?*sem); /** ?*?@brief?銷毀信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_destroy(sem_t?*sem); /** ?*?@brief?關閉信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?sem:?信號量實例地址 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_close(sem_t?*sem); /** ?*?@brief?分離信號量 ?* ?*?Detailed?function?description ?* ?*?@param[in]?name:?信號量名稱 ?* ?*?@return?成功返回0,失敗返回-1 ?*/ int?sem_unlink(const?char?*name);?
信號量基本API接口使用例子:父子進程間通信
?
#include? #include? #include? #include? #include? #define?SEM_NAME?"sem" int?main?(void) { ????int?sem_val?=?0; ????///?0) ????{ ????????///?
編譯、運行:
IPC總結
操作系統根據不同的場景提供了不同的方式,消息隊列、共享內存、UNIX域套接字、管道、信號量。
消息隊列: 內核中的一個優先級隊列,多個進程通過訪問同一個隊列,在隊列當中添加或者獲取節點來實現進程間通信。
共享內存: 本質是一塊物理內存,多個進程將同一塊物理內存映射到自己的虛擬地址空間中,再通過頁表映射到物理地址達到進程間通信,它是最快的進程間通信方式,相較其他通信方式少了兩步數據拷貝操作。
UNIX域套接字: 與TCP/IP套接字使用方式相同,但UNIX域套接字以文件系統作為地址空間,不需經過TCP/IP的頭部封裝、報文ack確認、路由選擇、數據校驗與重傳過程,因此傳輸速率上也不會受網卡帶寬的限制。
管道: 內核中的一塊緩沖區,分為匿名管道和命名管道。匿名管道只能用于具有親緣關系的進程間;而命名管道可用于同一主機上任意進程間通信。
信號量: 本質是內核中的一個計數器,主要實現進程間的同步與互斥,對資源進行計數,有兩種操作,分別是在訪問資源之前進行的p操作,還有產生資源之后的v操作。
審核編輯:湯梓紅
評論
查看更多