unix環境高級編程(下)-高級IO和進程間通訊篇

前言

筆者將《unix環境高級編程》主要內容總結爲三篇:文件篇進程篇高級io和進程間通訊三大板塊。本文是unix環境高級編程系列文章第三篇:高級IO和進程間通訊篇。該篇主要包括:linux

高級io

先介紹記錄鎖的概念和記錄鎖的數據結構。而後介紹阻塞io,非阻塞IO,異步io,IO多路轉接等概念,後者都是針對前者更優的技術。IO多路轉接技術包括:select,peslect,poll。最後介紹存儲映射IO。shell

進程間通訊

介紹了基本進程間通訊機制,包括兩大類:編程

  • 進程間數據共享:管道,FIFO,消息隊列和共享存儲
  • 進程間數據同步:信號量

網絡進程間通訊

介紹網絡間的進程通訊機制:套接字。首先是如何尋址。而後介紹socket編程的鏈接創建,數據傳輸等。數組

高級進程間通訊

高級進程間通訊提供一種能夠在進程間傳遞文件描述符的機制,包括STREAMS管道和unix域套接字服務器

一. 高級IO

1. 非阻塞IO

1.1 概念

  • 非阻塞io使得與磁盤io有關的系統調用永遠不會被阻塞
  • 這些io相關的系統調用有:open,read,write
  • 若是這種操做不能完成,則調用當即出錯返回

1.2 如何指定非阻塞io

  • 若是調用open得到文件描述符,可指定O_NONBLOCK標識
  • 對於已經打開的文件描述符,可調用fcntl,由該函數打開O_NONBLOCK標識

2. 記錄鎖

2.1 概述

  • 概念:當一個進程正在讀或修改文件的某個部分時,能夠阻止其餘進程修改同一文件區
  • flock:文件鎖,早期的unix只支持鎖整個文件,使用該函數
  • fcntl:記錄鎖,容許鎖文件中的任意字節數的區域

2.2 fcntl

  • cmd:
    • F_GETLK:獲取鎖信息
    • F_SETLK:設置鎖信息
    • F_SETLKW:阻塞版本的F_SETLK
  • flockptr:指向flock的指針
    struct flock{
        short l_type;//F_RDLCK共享讀鎖,F_WRLCK獨佔寫鎖,F_UNLCK解鎖
        off_t l_start;//加鎖區域其實位置
        short l_whence;//和start一塊兒肯定加鎖位置
        off_t l_len;//加鎖長度
        pid_t l_pid;//進程id
    }
    複製代碼
  • 不一樣鎖的兼容性:針對同一把鎖。若是不一樣鎖,新鎖老是覆蓋舊鎖

2.3 鎖的隱含繼承和釋放

  • 進程終止時,所創建的鎖所有釋放
  • 關閉文件描述符時,文件描述符引用的文件上的任何一把鎖都被釋放
  • fork產生的子進程不繼承父類設置的鎖
  • 執行exec後,新進程能夠繼承原程序的鎖

2.4 FreeBSD中記錄鎖的數據結構

  • v節點表的i節點結構串聯起全部的lockf結構
  • 每一個lockf結構說明了一個給定進程的一個加鎖區域
  • 在父進程中,關閉任意一個文件描述符,內核都會遍歷i節點各項lockf,並釋放持有的鎖

3. 系統v流機制

3.1 基本概念

  • STREAMS是系統V提供的構造內核設備驅動程序和網絡協議包的一種通用方法。不一樣於標準io中的stream
  • 流在用戶進程和設備驅動程序之間提供一條全雙工通路,流無需和實際硬件設備之間會話
  • 簡單流的基本結構:

3.2 STREAMS消息

  • STREAMS的全部輸入輸出都基於消息
  • 流首和用戶進程進行消息交換的函數:read,write,ioctl,getmsg,getpmsg,putmsg,putpmsg
  • 消息能夠順流而下,也能夠逆流而上
  • 消息的組成:消息類型,控制信息,數據。控制信息和數據由strbuf指定:
  • 消息約有25種,但通常使用的只涉及三種:
    • M_DATA:用戶數據
    • M_PROTO:協議控制信息
    • M_PCPROTO:高優先級協議控制信息
  • 每一個輸入STREAMS模塊有兩個輸入隊列,一個來自上面模塊的消息,另外一個來自下面模塊的消息
  • 流中的消息都有一個排隊優先級,經過優先級波段指定

3.3 putmsg和putpmsg

  • 用於將STREAMS消息寫入流中
  • 後者容許指定優先級波段

3.4 getmsg和getpmsg

  • 從流首讀STREAMS消息

4. IO多路轉接

4.1 阻塞io

  • 讀取一個文件描述符對數據,若是沒有數據就一直阻塞住
  • 缺點:長時間阻塞在同一個文件描述符,另外一個文件描述符雖然有不少數據卻得不到及時處理

4.2 非阻塞io

  • 將兩個文件描述符都設置爲非阻塞的
  • 對第一個文件描述符發送read,若是該輸入上有數據,則讀取並處理。如無數據則當即返回。
  • 第二個描述符重複上一步操做
  • 若干秒後,重複執行以上步驟,即輪詢
  • 缺點:浪費cpu時間,大多數時間實際上上無數據可讀的。輪詢的時間間隔也很難肯定

4.3 異步io

  • 當一個文件描述符已準備好能夠進行io時,用一個信號通知它
  • 缺點:併發全部的系統都支持,其次這種信號對每一個進程而言只有一個

4.4 IO多路轉接

  • 一種比異步IO更好的處理IO的技術
  • 先構造一張有關描述符的圖表,而後調用一個函數,直到這些描述符中至少一個準備好io時,該函數才返回。返回時,告訴哪些文件描述符已準備好能夠io
  • 支持IO多路轉接的函數:poll,pselect,select

4.5 select

  • readfds:可讀描述符集,每個文件描述符佔一位
    • 內部結構視圖
    • 描述符集的設置函數
  • maxfdp1:最大描述符+1,可設置爲FD_SETSIZE(1024)
  • writefds:可寫描述符集
  • exceptfds:異常描述符集
  • tvptr:願意等待的時間
    • NULL:永遠等待,捕捉到信號則中斷等待
    • 時間每一個字段爲0:徹底不等待,測試指定的文件描述符並當即返回
    • 不爲0:實際等待的時間
  • 返回值:
    • 返回-1:表示出錯,文件描述符沒有準備好時收到信號,此時不修改文件描述符
    • 返回0:已經超時了,指定都文件描述符都沒有準備好
    • 正數:已經準備好的文件描述符數量(每一個文件描述符讀寫單獨各算一次)

4.6 pselect

pselect與select相似,僅僅少部分有差別,以下:網絡

  • 超時值的數據結構不一樣
  • pselect超時值爲const,不可改變
  • 可以使用信號屏蔽字

4.7 poll

  • poll相似與select,不過接口有所不一樣
  • 不是爲每一個狀態構造文件描述符集,而是構造一個pollfd的數組,數組每一個元素指定文件描述符編號和關心的狀態
  • 參數:
    • events:用戶設置關心的事件
    • reevents:內核返回文件描述符事件

5. 異步IO

5.1 概述

異步io並不像select和poll對全部文件描述符都生效數據結構

  • SystemV系統:只對STREAMS設備和STREAMS管道起做用,發送SIGPOLL信號
  • BSD系統:只對終端和網絡起做用,發送SIGIO信號

5.2 SystemV異步IO

  • 啓動異步IO,須要調用ioctl,第二個參數爲I_SETSIG
  • 同時,在調用ioctl以前創建信號處理程序

5.3 BSD異步IO

異步IO是SIGIO(通用異步io)和SIGURG(通知網絡進程數據到達)兩個信號的組合併發

  • 調用signal或signalaction爲SIGIO創建信號處理程序
  • 以命令F_SETOWN調用fcntl設置進程id和進程組id,將接收對於該描述符的信號
  • 以命令F_SETFL調用fcntl設置O_ASYNC文件狀態標識,使文件描述符上能夠進行異步IO

6. readv和writev

  • 用於在一次函數調用中讀寫多個非連續的緩衝區

7. readn和writen

  • 按需多此調用read和write,直至讀寫了N各字節數據
  • 使用與讀寫管道,網絡設備或終端數據

8. 存儲映射IO

  • 使一個磁盤空間與一個存儲空間中的緩衝區映射。當從緩衝區取數據,就至關於讀文件中的相應字節。寫數據到緩衝區至關於自動寫入文件。這樣就能夠不用read和write的狀況下執行io
  • 文件映射到存儲區:
    • addr:存儲映射起始地址,一般設置爲0,表示由系統選擇地址而後做爲返回值返回
    • port:說明對存儲映射區的保護要求,權限不能超過文件自己權限
      • PORT_READ:映射區可讀
      • PORT_WRITE:映射區可寫
      • PORT_EXEC:映射區可執行
      • PORT_NONE :映射區不可訪問
    • flag:
      • MAP_FIXED:返回值必須等於addr,不利於移值
      • MAP_SHARED:存儲操做的配置
      • MAP_PRIVATE:建立私有副本
  • 更改存儲映射區權限:mprotect
  • 刷新映射存儲區:msync
  • 解除存儲映射區:munmap

二. 進程間通訊

進程間通訊機制包括:dom

  • 經典IPC:管道,FIFO,消息隊列,信號量,共享存儲
  • 網絡IPC:套接字

1. 管道

1.1 概述

  • 最古老的ipc機制
  • 管道有兩個侷限性:
    • 歷史上,它是半雙工的,即數據只能在一個方向流動。雖然如今某些系統提供全雙工,可是爲了移植性,不假定它有此特性
    • 他們只能在具備公共祖先的進程之間使用
  • 儘管有侷限性,半雙工管道仍然是最經常使用的ipc
  • 若write寫一個尚無進程爲讀而打開的管道,產生SIGPIPE信號
  • 若管道的最後一個寫進程關閉該管道,則爲管道的讀進程產生文件結束標識

1.2 管道的建立

  • 參數fields傳入兩個文件描述符,field[0]爲讀而打開,field[1]爲寫而打開,field[1]的輸出是field[0]的輸入
  • 管道模型:

1.3 popen和pclose

  • popen先執行fork,而後調用exec以執行cmdstring,並返回標準io文件指針。若是type=「r「,文件指針鏈接到cmdstring的標準輸出。若是type=「w」,文件指針鏈接到cmdstring的標準輸入
  • pclose關閉標準io流

1.4 FIFO

  • FIFO也成爲命名管道,經過FIFO,不相關的進程也能交換數據
  • 建立FIFO:
  • mode參數與open函數一致
  • 非阻塞標準O_NONBLOCK:
    • 沒有指定該參數:只讀open要阻塞到某個其餘進程爲寫而打開此FIFO
    • 指定該參數:只讀open當即返回。沒有進程打開FIFO,將出錯返回-1
  • 相似與管道,若write寫一個尚無進程爲讀而打開的FIFO,產生SIGPIPE信號。若FIFO的最後一個寫進程關閉該FIFO,則爲FIFO的讀進程產生文件結束標識
  • PIPE_BUF說明了可被原子寫到FIFO的最大數據量
  • FIFO的用途
    • 由shell命令使用,以便將數據從一條管道線傳到另外一條,無需建立中間臨時文件
    • 用於客戶-服務器進程中,以在客戶進程和服務器進程間傳遞數據

2. XSI IPC

消息隊列,信號量和共享存儲,這三種IPC稱作XSI IPC,他們之間有不少共性,包括:異步

2.1 標識符和鍵

  • 標識符:惟一標識IPC對象的內部名,非負整數
  • 鍵:IPC對象的外部名,使多個合做進程能在同一個IPC對象上會合。鍵基本數據類型爲key_t
  • 客戶進程和服務器進程在同一IPC上會合的方法:
    • 服務器進程指定鍵IPC_PRIVATE建立一個新的IPC結構,將返回的標識符放到某處(文件)給客戶進程使用。缺點:要分別讀寫文件
    • 在公共頭文件中定義一個鍵,服務器進程指定該鍵建立IPC結構。缺點:可能IPC已經存在,獲取時會出錯
    • 客戶進程和服務器進程認同一個路徑名和項目id,接着調用ftok將兩個值變換爲鍵,再調用方法2

2.2 權限結構

  • XSI IPC爲每一個IPC結構設置了一個ipc_perm結構,規定了權限和全部者。

2.3 結構限制

  • 三種形式的IPC都有內置限制

2.4 優勢和缺點

缺點

  • IPC結構是在系統範圍內起做用的,沒有訪問計數
  • IPC結構在文件系統中沒有名字,不能修改屬性,不能ls查看IPC對象,不能用rm刪除,也不能用chmod修改權限。不能用文件描述符,也就不能使用select,poll模型

優勢

  • 可靠
  • 流是受控的:緩衝區資源緊張,進程就休眠
  • 面向記錄
  • 能夠用非先進先出方式處理

特徵對比

3. 消息隊列

3.1 概述

  • 消息的連接表,存放在內核中,由消息隊列標識符標識
  • 最開始出現的爲了提供比通常IPC更高速度的通信方式,但如今速度上沒有優點,已經再也不使用了
  • 建立或打開隊列:msgget
  • 發送消息:msgsend
  • 獲取消息:msgrcv,不必定先進先出,可按消息的類型字段取

3.2 數據結構

  • 每一個隊列相關的數據結構
  • 消息隊列在各個系統中的參數限制

3.3 msgctl函數

msgctl函數對隊列執行多裝操做(相似於ioctl,垃圾桶函數)

  • cmd:要執行的命令
    • IPC_STATE:獲取msgid_ds結構,並放入buf參數
    • IPC_SET:按buf值,設置數據
    • IPC_RMID:刪除隊列和數據

3.4 msgsend函數

  • ptr:指向消息內容指針,消息的組成:
    • 類型:正長整型類型
    • 長度
    • 實際數據
  • flag:標誌
    • IPC_NOWAIT:非阻塞io

3.5 msgrcv函數

  • ptr:獲取的數據地址,包括類型和實際數據
  • nbytes:數據緩衝區長度
  • type:獲取哪一種消息。
    • type=0:返回隊列中第一條消息
    • type>0:返回消息類型爲type的第一個消息
    • type<0:返回消息類型小於等於type絕對值的消息
  • flag:
    • IPC_NOWAIT:非阻塞

4. 信號量

4.1 概述

  • 信號量不一樣於管道和消息隊列,它是一個計數器,用於多進程堆共享數據對象的訪問
  • 信號量計數操做必須是原子的,一般在內核中實現
  • 使用信號量獲取共享資源的操做
    • 測試該資源的信號量N
    • 若N爲正,則進程可使用該資源。而後N=N-1,表示使用了一個資源單位
    • 若N=0,則進程休眠,直到N>0才喚醒,而後第一步
    • 當進程不使用共享資源時,N=N+1,若是有進程在休眠等待則喚醒
  • XSI信號量相對複雜一些
    • 信號量併發單個非負值,而是一個或多個信號量值的集合
    • 建立信號量和賦值是分開的,不能原子的建立信號集合
    • 即便沒有進程在使用信號量,他仍然存在
  • 得到一個信號量ID:semget

4.2 數據結構

  • 內核爲每一個信號量集合設置了一個semid_ds結構
  • 每一個信號量的結構
  • 信號量的系統限制

4.3 semctl函數

  • 包含多種信號量操做
  • cmd:
    • IPC_STAT:取semid_ds結構
    • IPC_SET:設置數據
    • IPC_RMID:刪除信號量集合

4.4 信號量與記錄鎖在liunx的對比

  • 記錄鎖比信號量耗時
  • 但若是隻鎖一個資源,寧肯用記錄鎖。由於他使用簡單,進程終止時會自動清理鎖

5. 共享存儲

5.1 概述

  • 共享存儲容許兩個或更多進程共享給定的存儲區
  • 數據不須要在進程間複製,是最快的IPC
  • 多進程對於同一個存儲區,要注意同步訪問,一般使用信號量來進行同步
  • 獲取共享存儲區域id:shmget
  • 共享存儲的位置:棧下面

4.2 數據結構

  • 內核爲每一個共享存儲段設置了shmid_ds結構
  • 共享存儲的系統限制

4.3 shmctl函數

  • 包含堆共享存儲的多種操做
  • 參數同前面

4.4 共享存儲的使用

  • shmat函數:進程用於鏈接共享存儲到其餘的地址空間中
  • addr參數:
    • 爲0:鏈接到由內核選擇的能夠地址上,推薦方式
    • 非0:且沒有指定SHM_RND,鏈接到該地址
    • 非0:指定SHM_RND,將地址向下取最低邊界地址倍數
  • flag:
    • SHM_RDONLY:只讀
    • 其餘:讀寫

4.5 共享存儲的釋放

  • shmdt:脫離該段,但並不刪除數據,標識符還在,直到調用shmctl刪除

三. 網絡進程間通訊:套接字

1. 套接字描述符

  • 套接字是通訊端點的抽象,是用文件描述符實現的
  • 建立套接字描述符:
    • domain:套接字域
    • type:套接字類型
    • protocol:協議,一般爲0。表示根據套接字類型默認選擇協議
  • 關閉套接字:close
  • shutdown:禁止套接字上的輸入/輸出,可只關閉一個方向

2. 尋址

2.1 字節序

  • 大端字節序:最大字節地址對應於數字最低有效字節
  • 小段字節需:最小字節地址對應於數字最低有效字節
  • 各個平臺的字節序以下:
  • 網絡傳輸中:tcp/ip使用大端字節序

2.2 地址格式

  • 地址標識了套接字端點,通用地址格式爲:
struct sockaddr{
        sa_famliy_t sa_famliy;
        char        sa_data[];
    }
複製代碼
  • 套接字實現能夠自由添加aa_data字段以及長度
//linux實現
    struct sockaddr{
        sa_famliy_t sa_famliy;
        char        sa_data[14];
    }
    //freeBSD實現
     struct sockaddr{
        unsigned char sa_len;
        sa_famliy_t sa_famliy;
        char        sa_data[14];
    }
複製代碼
  • ipv4套接字通用地址:<netinnet/in.h>,實現者能夠自由添加額外字段
  • ipv6套接字通用地址:實現者能夠自由添加額外字段
  • sockaddr_int和sockaddr_int6都會被轉化爲sockaddr結構傳入套接字例程中
  • 二進制地址與文本格式地址轉化:inet_ntop,inet_pton

2.3 地址查詢

  • 查找給定計算機主機信息:gethostent
  • 返回的主機信息數據結構:
  • 獲取網絡名字和網絡號
  • 獲取協議名字和協議號
  • 服務名字和端口號映射關係查詢
  • 將主機名和服務名映射到一個地址
  • 地址信息包含的成員

2.4 將套接字與地址綁定

  • 客戶端套接字關聯地址沒有太大意義,可讓系統選一個默認地址
  • 服務端須要給一個客戶端請求的套接字綁定一個衆所周知的地址
  • 客戶端綁定服務端地址的方法:

3. 創建鏈接

3.1 connect

  • connect爲客戶端調用,用於鏈接請求
  • addr爲服務器地址
  • 若是sockfd沒有綁定地址,connect會給調用者綁定一個默認地址
  • 鏈接可能失敗,應用程序必須能處理connect返回的錯誤

3.2 listen

  • listen爲服務端調用
  • 服務器用listen宣告能夠接受鏈接請求
  • backlog:鏈接請求數量

3.3 accept

  • accept得到鏈接請求,並創建鏈接
  • 返回的文件描述符是套接字描述符,描述符鏈接到調用connect到客戶端
  • 新的套接字描述符和原始套接字sockfd具備相同的套接字類型和地址族
  • 傳給accept的原始套接字沒有關聯到這個鏈接,而是繼續保存能夠狀態並接受其餘鏈接請求
  • 若是沒有鏈接請求等待處理,accept會阻塞直到有請求到來

4. 數據傳輸

4.1 send

  • 發送數據,相似與write函數
  • send比write多了第四個參數flags,用於改變處理數據到傳輸方式
    • MSG_DONTROUTE:勿將數據路由出本地網絡
    • MSG_DONTWAIT:容許非阻塞操做
    • MSG_EOR:記錄結束
    • MSG_OOB:外帶數據
  • sendto函數:相似send。可是sendto容許在勿鏈接到套接字上指定一個目標地址

4.2 recv

  • 獲取數據,相似於read函數
  • recv比read多了第四個產生flags,用於控制如何接收數據
    • MSG_OOB:接受外帶數據
    • MSG_PEEK:返回報文內容而不真正取走報文
    • MSG_TRUNC:即便報文被截短,也返回實際的長度
    • MSG_WAITALL:等待直到因此數據可用

5. 套接字選項

5.1 套接字選項包括

  • 通用選項,工做在全部套接字類型上
  • 在套接字層次管理的選項,可是依賴底層協議的支持
  • 特定與某種協議的選項,爲某個協議獨有

5.2 設置套接字的函數

6. 帶外數據

  • 帶外數據是一些通訊協議支持的可選特徵,容許高優先級的數據比普通數據優先傳輸
  • TCP將外帶數據成爲「緊急數據」

四. 高級進程間通訊

1. 概述

  • Streams管道和unix套接字,這兩種高級IPC,能夠在進程間傳遞文件描述符
  • 服務進程可使他們的打開文件描述符與特定的名字相關聯
  • 客戶進程可使用這些名字與服務器通訊
  • 操做系統會爲每一個客戶進程提供一個獨自的IPC通道

2. STREAMS管道

  • Streams pipe是一個全雙工(雙向)通道
  • 內部結構以下

3. UNIX域套接字

  • 用於在同一臺機器上運行的進程之間通信
相關文章
相關標籤/搜索