什么是進程
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操作。
審核編輯:湯梓紅
電子發燒友App















評論