操作系統之進程間通信


    進程間通信:
/************************    
    當出現白色的a.out文件且無法在虛擬機和電腦上刪除該文件時:
    1.到該目錄下編譯指令    【ps -ef】找到該.out進程的進程號
    2.用指令    【kill -9 該進程的進程號】    終止該進程
***************************/    
    
1.基本概念:
    兩個/多個進程之間數據交流/交換,稱爲進程間通信
    由於進程的地址空間是相互獨立的,進程間通信,就需要其它機制
    
2.通信方式:
    1.信號
    2.管道    

    3.共享內存
    4.消息隊列
    5.信號量
    
    6.套接字
——————————————————————————
    
一.信號:【給進程用的,進程使用信號就像人使用手機短信】

1.中斷:
    表示暫時停止當前進程的執行轉而去執行新的程序或處理意外情況的過程,叫做中斷
    中斷分爲硬件中斷和軟件中斷

2.基本概念:
    信號的本質就是軟中斷,它可以作爲進程中通信的一種機制,更重要的是,信號總是中斷一個
    【正在執行】的程序,它更多的被用於處理意外情況。
    
3.基本特點:
    1.信號是異步的,進程不知道信號什麼時會到
    2.進程既可以處理信號,也可以忽略,還可以發送信號給其它進程
    3.每個信號都有一個名字,並且使用SIG開頭
    
4.基本命令
    kill -l //查看系統所支持的所有信號
    常用信號:
    ctrl+c     發送信號SIGINT    2    默認處理方式爲終止進程
    ctrl+\    發送信號SIGQUIT    3    ...
    【kill -9    發送信號SIGKILL    9    ...】
    kill  -9 pid 指令用於結束ID號爲pid的進程        【非常常用】
    
    7     SIGBUS    總線錯誤
    11    SIGSEGV    段錯誤
    13    SIGPIPE    管道破裂
    14    SIGALRM    定時器信號
    17    SIGCHILD 子進程結束時給父進程發的信號
        
5.信號的分類
    一般來說,linux支持的信號範圍是1-64,不保證連續
    而unix支持的信號範圍是1-48
    其中1-31之間的信號,叫不可靠信號,不支持排隊,信號可能丟失,也叫非實
    時信號 
    34-64之間的信號,叫可靠信號,支持排隊,不丟失,也叫實時信號
    
    生成信號的事件來分:程序錯誤(如非法訪問內存)、外部事件(ctrl+c)、顯示
    請求(kill -9)

6.處理信號 
    1.默認處理        絕大多數的信號默認處理方式都是終止進程
    2.忽略處理        SIGSTOP和SIGKILL不能被忽略
    3.自定義處理    只要寫一個信號處理函數即可    
注:    
    自定義處理即使用    signal(SIGCHLD,sigHander);
    若程序中沒有該語句,則視爲對SIGCHLD信號作默認處理【終止進程】
    若有該語句,則當SIGCHLD信號出現時,程序將執行sigHander函數,
    而不會作默認處理【不會終止進程】
    
信號的使用步驟:
1.寫一個信號處理函數
【即封裝一個函數,當信號來了時需要做什麼就寫入該函數中】
void sigHander(int signo)
{
    //信號來了時要做的工作
}
signo:信號的數值【1-64】

2.註冊該信號函數        signal
#include <signal.h>

typedef void (*sighandler_t)(int);//函數指針

    sighandler_t signal(int signum, sighandler_t handler);
    功能:向系統註冊信號處理函數(即第二個參數),只要有信號到來,系統
    就會自動調用函數指針對應的函數
例如:
    signal(SIGCHLD,sigHander);
    SIGCHLD:信號的名稱/數值
    sigHander:信號的處理方式【函數指針    自定義的處理方式】
其中,信號的處理方式分爲三種:
        SIG_IGN        忽略處理
        SIG_DFL        默認處理【一般效果都是終止進程】
        函數指針    自定義的處理方式
注:若是不註冊信號函數,則程序碰到這個信號時就按照默認處理來
處理這個信號!!
若是註冊了這個信號,就按照註冊的方法處理這個信號【分三種】

3.設置鬧鐘,等過了指定時間後再發送信號
    unsigned int alarm(unsigned int seconds);
    功能:主要用於根據參數指定的秒數之後發送SIGALRM信號
    返回值:成功返回上一個鬧鐘沒有來得及響的秒數,之前沒有鬧鐘則返回0
    當參數爲0時,則之前設置的鬧鐘會被取消
    例如:
    int res = alarm(10);            //10秒後發送時鐘信號    
    printf("res = %d\n",res);//打印上一個時鐘沒來得及相應的秒數    【0】
    sleep(2);    //休眠2秒
    res = alarm(5);        //5秒後發送時鐘信號        並返回上一個時鐘沒來得及相應的秒數
    printf("res = %d\n",res);//打印上一個時鐘沒來得及相應的秒數    【10-2=8】    
    sleep(3);
    res = alarm(0);    //取消之前設置的鬧鐘                                                             
    printf("res = %d\n",res);//    打印上一個時鐘沒來得及相應的秒數    【5-3=2】                                                     
    //信號發送前取消信號將不會發送時鐘信號                                                             
                                                                 
4.等待一個信號,會導致調用進程休眠,直到收到一個信號
    int pause(void);                                                                     
    功能:用來等待一個信號,會導致調用進程休眠,直到收到一個信號
    例如:
    pause();    //死等一個信號的到來
    printf("hello world\n");    //信號到來後打印hello world
    注意:該信號必須要以自定義處理方式註冊,
    否則該信號到來後將會終止進程,使等待變得毫無意義
                                                                 
5.給正調用的進程發送參數指定的信號                                                                 
    int raise(int sig);
    sig:信號名
    功能:主要用於給正調用的進程發送參數指定的信號                                                             
    例如:raise(kill);
        //殺死正在調用的進程【自殺】
                                                                 
6.給指定的進程發送指定的信號                                                                 
    int kill(pid_t pid, int sig);                                                             
    功能:主要用於給指定的進程發送指定的信號                                                             
    例如:        kill (pid,signo);                                                             
    pid    :進程號        
    signo:信號名

    raise(int sig);    與    kill(getpid(), sig);    等價

7.system    在程序中調用系統命令(和shell命令)。 
 system("pause")就是從程序裏調用“pause”命令; 
 而“pause”這個系統命令的功能很簡單,就是在命令行上
 輸出一行類似於“Press any key to exit”的字,等待用戶按一個鍵,
 然後返回。

二.管道【也稱管道文件,本質是一個文件】
基本概念
    【管道本質】還【是】以【文件】作爲通信的媒介,該文件比較特殊,稱爲管道文件
    管道分爲兩類:
        無名管道:由內核創建,實現具有親緣關係(父子或兄弟)的進程之間的通信       pipe
        
        有名管道:由程序員創建,實現任意兩個進程之間的通信                        FIFO
        
    管道的創建與說明:
        管道是基於文件描述符的通信方式,當一個管道建立時,它會創建兩個文件描述符fds[0]和fds[1]
        其中,fds[0]固定用於讀管道,fds[1]固定用於寫管道,這樣構成一個半雙工的通道。
        
        管道關閉時,只需將這兩個文件描述符關閉即可。
        
管道的使用步驟:

1.創建無名管道【本質是生成一個(管道文件)】        pipe();
    int pipe(int pipefd[2]);
    功能:用來在內核中創建一個無名管道,
    實現具有親緣關係(父子或兄弟)的進程之間的通信 
    
    返回值:    成功:返回 0
                失敗:返回 -1  ,errno被設置    
    如果創建成功,則會 把讀和寫的文件描述符返回給pipefd[0]和pipefd[1];            
    例如:
        int pipefd[2];
        int res = pipe(pipefd);
        pipefd:    型數組,用來存放管道的讀、寫文件描述符
        pipefd[0]:用於讀管道
        例如:read(fds[0],buf,res);
        //從管道文件中讀res個字節到buf區
        
        pipefd[1]:    用於寫管道
        例如:res = write(fds[1],str,strlen(str));
        //將str中數據寫入管道文件中
注:
        1.pipe創建的管道是阻塞式的
            當【讀】管道時,如果管道中沒有數據,則阻塞,直到有數據到來
            當【寫】管道時,如果管道緩衝區滿,則阻塞,直到有進程讀起數據,則繼續寫
            
        2.    數據寫到管道中,內核會把這些數據緩存,直到有進程來讀
            
        3.    寫端對讀端的依賴性:
                只有在管道的讀端存在時,向管道寫數據纔有意義,否則,向管道中寫數據的進程將收到內核傳
                來的信號SIGPIPE【管道破裂】,應用程序可以處理該信號,也可以忽略(默認是終止進程)
                //當管道的讀端關閉時,往管道里寫數據會造成管道破裂,
                //即會發出信號SIGPIPE,默認會終止進程        
        
2.創建有名管道【本質是生成一個(管道文件)】    mkfifo()    ;
    http://www.cnblogs.com/TsengYuen/archive/2012/05/16/2504102.html
    
    int mkfifo(const char *pathname, mode_t mode);    
    功能:創建一個有名管道    ,實現任意兩個進程之間的通信
        
    返回值:    成功:返回 0
                失敗:返回 -1  ,errno被設置    
    例如:    
    int res = mkfifo("/home/fifo114",0664);    
    /home    :內存中確實存在的一個目錄的絕對路徑    
    fifo114    :創建的管道文件的文件名    
    "/home/fifo114"    :在home目錄下創建一個管道文件    
    0664        :該管道文件的權限    
    /**************************
    最開始”/home“下並不存在管道文件"fifo114"
    執行代碼mkfifo("/home/fifo114",0664);後
    路徑”/home/csgec“下才存在管道文件"fifo114"
    ****************************/    
無名管道和有名管道的對比:
1.作用:
        無名管道:只能實現具有情緣關係的進程間的通信
        有名管道:可以實現任意兩個進程間的通信
2.創建方式:
        無名管道:需要提供一個int 型數組,用來存放管道的讀、寫文件描述符
            int pipefd[2];
            int res = pipe(pipefd);
        有名管道:需要提供一個內存中確實存在的一個目錄的絕對路徑
            int res = mkfifo("/home/fifo114",0664);    
3.通信方式【即如何讀寫    】:    
        無名管道:不需要打開管道文件,可以直接對文件進行讀寫
            pipefd[0]:用於讀管道
            例如:read(fds[0],buf,res);
            //從管道文件中讀res個字節到buf區            
    —        pipefd[1]:    用於寫管道
            例如:res = write(fds[1],str,strlen(str));
            //將str中數據寫入管道文件中    
        有名管道:必須先打開管道文件才能對文件進行讀寫
            int fd = open("/home/csgec/fifo114",O_WRONLY);
            //打開管道文件,權限只寫    
            char *str = "hello world!";
            res = write(fd,str,strlen(str));
            //將str中數據寫入有名管道所在文件    
    —        int fd = open("/home/csgec/fifo114",O_RDONLY);
            //打開有名管道所在文件,權限只讀    
            char buf[1024] = {0};
            res = read(fd,buf,sizeof(buf))    ;
            //從有名管道讀取數據到buf中    
    
三.共享內存        share memory
【本質】:    是屬於內核的一塊內存空間,這塊空間由內核維護和管理,
                但是其他進程可以通過內存映射的方式對該內存進行操作
                (進程擁有的內存是4G的虛擬內存,若不與內核的內存建立映射關係,
                就不能對內核產生影響,操作也毫無意義。)
基本概念:
    共享內存是以一塊內存作爲通信的媒介,這塊內存由內核維護和管理,允許其他進程映射。
    共享內存是進程間通信方式中效率最高的。
    共享內存最大的問題就是多個進程同時修改時,很難控制。
    
共享內存的使用步驟:
1.獲取key        ftok();
【本質】:key是一個IPC,由ftok函數利用【實際存在的文件的identity(即文件id),
並將其最後8bit位同proj_id做與運算】生成

    key_t ftok(const char *pathname, int proj_id);
    key_t ftok("文件名",'整形的項目ID');    //a也是整數
    功能:ftok函數利用實際存在的文件的identity(即文件id),並將其最後8bit位
    同proj_id做與運算生成一個唯一的IPC key了。這個key可代表system v的消息隊列、
    信號燈、共享內存的描述符。
    返回值:成功:返回key_t類型的key,
                失敗:返回-1 ,errno被設置
    例如:key_t key = ftok(".",'a');    //獲取KEY
    ".":字符串形式的文件路徑,要求必須存在且可訪問
    'a':整型的項目ID,要求非0,低8位被使用,一般寫一個字符

2.通過key獲取一塊共享內存        shmget();
    int shmget(key_t key, size_t size, int shmflg);
    即: shmget(key,共享內存大小,權限);
    例如:int shmid = shmget(key,1024,0666 | IPC_CREAT);

    功能:主要用於創建/獲取共享內存
    返回值:成功返回shmid,(類似打開/創建文件時獲得的文件描述符)
            失敗返回 -1 ,errno被設置
    key:標識共享內存的鍵值(類似於文件名稱),一般通過ftok函數來獲取鍵值
    size:要獲取的共享內存的大小
    shmflg:標誌包括IPC_CREAT與IPC_EXCL
                與open()函數的O_CREAT與O_EXCL類似
            IPC_CREAT        如果共享內存不存在,則創建,否則,直接打開
            IPC_EXCL        與IPC_CREAT結合使用,如果存在,則報錯
                                    (IPC_CREAT | IPC_EXCL)
        注:當新建共享內存時,需要在第三個參數中指定權限,一般    0666 | IPC_CREAT | IPC_EXCL
        
3.相關指令
        ipcs -m    
        //顯示所有共享內存段
------------ 共享內存段 --------------
鍵                    shmid      擁有者      權限             字節     連接數        狀態      
0x00000000 1179648    gec        600        524288     2          目標       

        ipcrm -m id
        //移除共享內存段id【id即爲查看到的shmid】

4.映射共享內存        *shmat();
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    即:*shmat(共享內存ID,共享內存映射到的目標地址,標誌);
    功能:用於映射共享內存到用戶空間
    返回值:    成功:返回共享內存的映射地址
                失敗:返回 -1,errno被設置

    例如:void *addr = shmat(shmid,NULL,0);
    //    將共享內存shmid的ID映射到NULL,該內存可讀可寫
    //生成新指針,指向該地址
    shmid:獲取到的共享內存的ID,shmget函數的返回值
    NULL:將共享內存映射到指定地址,一般給NULL即可
    0:標誌,默認給0,表示可讀可寫

5.操作共享內存
    寫:    char *str = "hello world";
        1.    memcpy(addr,str,strlen(str));
        2.    strcpy(addr,str);        
        3.    sprintf(addr,"%s",str);
    讀:printf("buf =%s\n",(char *)addr);

6.解除共享內存        shmdt();
【目的】解除共享內存是爲了防止在以後使用進程的時候對內核進行誤操作
    int shmdt(const void *shmaddr);
    即:shmdt(指向需要解除的共享內存的指針);
    功能:用於解除映射
    例如:shmdt(addr);
    //解除addr指向的內存
    addr:shmat的返回值【映射到的地址】
        
7.刪除共享內存對象        shmctl();
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    即:shmctl(共享內存的ID,cmd操作命令,結構體指針);
    功能:用於對指定的共享內存執行指定的操作
    返回值:    成功:返回0
                失敗:返回-1,errno被設置
    例如:shmctl(shmid,IPC_RMID,NULL);            
    shmid:共享內存的ID,shmget函數的返回值                                    
    IPC_RMID:cmd操作命令
        IPC_RMID    刪除共享內存對象,此時第三個參數給NULL即可
    NULL:結構體指針

四.消息隊列
/*****************************
發送消息和接收消息時,key的值一樣,所以要用同一條代碼去生成key
如:    key_t key = ftok("/home",'b');
其中路徑必須存在!!!
*****************************/
【本質】:是【系統內核中的】一個用來存放消息的【隊列】
【工作原理】:    【將數據打包成消息,將消息放到隊列中】,
                        使【兩個進程通過訪問這個隊列】從而實現進程通信。
基本概念
    消息隊列就是系統內核中保存的一個用來【將數據打包成消息,
    將消息放到隊列中】,使【兩個進程通過訪問這個隊列】從而實現通信。
    
消息隊列的使用步驟
1.創建/獲取消息隊列            msgget();
    int msgget(key_t key, int msgflg);
    即:msgget(key,消息隊列的創建標誌);
    功能:主要用於創建/獲取消息隊列 
    返回值:成功:返回創建的消息列隊的ID
                失敗:返回-1,errno被設置
    例如:int msgid = msgget(key,0666|IPC_CREAT);
        //得到一個消息隊列的ID,用msgid保存
    key:標識共享內存的鍵值,即ftok函數的返回值                                
    0666|IPC_CREAT:消息隊列 的創建標誌
        IPC_CREAT        如果共享內存不存在,則創建,否則,直接打開
        IPC_EXCL            與IPC_CREAT結合使用(IPC_CREAT | IPC_EXCL),如果存在,則報錯
        0 獲取已存在的消息隊列
    注:當新創建消息隊列時,需要指定權限,如0666|IPC_CREAT    
    
2.生成一個消息【即定義一個消息類型的結構體,並給其賦值】
    struct msgbuf msg;    //定義一個消息型結構體變量用來存儲要發送的消息
    msg.msgtype = 1;        //該消息類型賦值爲1
//    msg.buf = "hello";        //除了初始化之外,不能直接整體給數組賦值
    strcpy(msg.buf,"hello world");    //賦值消息的內容


    
3.發送消息        msgsnd();        
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    即:msgsnd(得到的一個消息隊列的ID,消息的地址,消息的大小,發送標誌);
    功能:用於向指定的消息隊列發送指定的消息
    例如:int res = msgsnd(msgid,&msg,sizeof(msg.buf),0);
    //將地址msg中的內容的sizeof(msg.buf)個字節以阻塞的方式發送給ID爲msgid的消息隊列
    msqid:msgget函數的返回值
    &msg:消息的地址(消息從哪裏來)
        消息的一般形式:
        struct msgbuf
        {
            long msgtype;//消息類型,需要大於0
            char mtext[1];//消息的內容,可以使用其它的數據類型
        };
    sizeof(msg.buf):消息的大小(指消息的大小,不包括類型)
    0:發送標誌,一般給0(阻塞直到發送成功)    

4.接收消息            msgrcv();
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
    即:msgrcv(得到的一個消息隊列的ID,消息的地址,消息的大小,消息的類型,接受的標誌);
    功能:用於接收消息
    例如:struct msgbuf msg;    
    //定義一個消息型結構體變量用來存儲接收到的消息
    msgrcv(msgid,&msg,sizeof(msg.buf),0,0);
    //以阻塞的方式接收消息隊列msgid中的sizeof(msg.buf)個類型爲0的消息,
    //並存入消息型結構體變量msg中
    msgid:msgget函數的返回值
    &msg:消息的地址(消息保存到哪裏去)
    sizeof(msg.buf):消息的大小
    0:消息的類型
        0         表示接受隊列中的第一個消息
        >0         表示接受隊列中第一個類型爲msgtyp的消息
        <0         表示接受隊列中第一個小於等於msgtyp絕對值的消息,
        其中最小的類型優先讀        
    0:接受的標誌, 一般給0    //0代表阻塞

5.刪除隊列            msgctl();            
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    即:msgctl(得到的一個消息隊列的ID,cmd操作命令,結構體指針);
    功能:用於對指定的消息隊列 執行指定的操作
    返回值:    成功:返回0
                失敗:返回-1,errno被設置
    例如:msgctl(msgid,IPC_RMID,NULL);
    msgid: msqid,msgget函數的返回值
    IPC_RMID: cmd操作命令
        IPC_RMID    刪除消息隊列,此時第三個參數給NULL即可
    NULL:結構體指針

6.相關指令:    
        ipcs -q
--------- 消息隊列 -----------
鍵        msqid      擁有者  權限     已用字節數 消息  
          
        ipcrm -q id 
        //移除消息隊列id【id即爲查看到的msqid】

五.信號量    
/*********************
信號量就相當於共享內存中的一個盒子,
把信號量初始化成多少就是最開始的時候往盒子裏放了多少個信號!
P操作就是從盒子裏拿信號,只有盒子裏有信號時才能拿,
若盒子裏沒有信號,你就只能在盒子旁邊等到什麼時候盒子裏有信號了,拿到信號才能走!
V操作就是往盒子裏放信號,不管盒子裏有沒有信號,你都可以往裏面放信號!
*********************/
【本質】:信號量是在計算機中用來模擬信號燈的一個【整數】
當這個整數變爲非0才能進行某種操作許可;
在進行操作的同時,將該整數減1,以此改變信號燈,
將減1操作稱爲P操作,也稱獲取信號;
當該操作進行完之後,將該整數加1,以此來恢復信號燈,
將加1操作稱爲V操作,也稱釋放信號;    
    
【原理】:可以模擬爲一下代碼:
int i=N;        //初始化信號量
if(i != 0)
{
    i--;    //可以進行P操作
    //i++    //也可以進行V操作
    進行某種操作.....
    i++;    //V操作
}
else    if(i == 0)
{
    i++;    //只能進行V操作
}

【1】.進程中信號量的使用步驟
1.獲取key,使用函數ftok()
key_t key=ftok("/home",'a');
"/home":字符串形式的文件路徑,要求必須存在且可訪問
'a'    :整型的項目ID,要求非0,低8位被使用,一般寫一個字符

2.創建/獲取信號量,使用函數semget()函數
int semid = semget(key,1,0777 | IPC_CREAT);
key:    ftok的返回值
1:        信號量的個數
0777 | IPC_CREAT:    標誌,可用    IPC_CREAT   IPC_EXCL    
注:創建新的信號量時,需要指定權限,如0664 | IPC_CREAT

3.初始化信號量,使用函數semctl()函數
int res = semctl(semid,0,SETVAL,1);
if(res < 0)
{
    perror("semctl");
    exit(-1);
}
semid:semid,semget的返回值
0:信號量的下標
SETVAL:操作命令
    IPC_RMID        刪除信號量,不需要第四個參數
    IPC_SETVAL        使用第4個參數的值初始化信號量
1:可變參數,是否需要由第三參數決定 

4.定義結構體指針
    struct sembuf
    {
        unsigned short sem_num;  /* 信號量所在下標 */
        short sem_op;   /* 具體操作,正數增加,負數減少*/
        short sem_flg;  /* 標誌 IPC_NOWAIT and SEM_UNDO. */
    };

5.初始化結構體指針
P操作:
sb.sem_num = 0;        
sb.sem_op = -1;        //P操作將其初始化爲-1
sb.sem_flg = SEM_UNDO;
V操作:
sb.sem_num = 0;
sb.sem_op = 1;            //V操作將其初始化爲1
sb.sem_flg = SEM_UNDO;

6.操作信號量,使用函數semop()函數
int res = semop(semid,&sb,1);
if(res < 0)
{
    perror("semop");
    exit(-1);
}
semid:獲取的信號量
&sb:結構體指針
    struct sembuf
    {
        unsigned short sem_num;  /* 信號量所在下標 */
        short sem_op;   /* 具體操作,正數增加,負數減少*/
        short sem_flg;  /* 標誌 IPC_NOWAIT and SEM_UNDO. */
    };
1:結構體指針指向的結構體數組的元素個數

ps:訪問共享資源

7.刪除信號量,使用函數semctl()函數
int semctl(semid,0,IPC_RMID);
semid:獲取的信號量
0:信號量的下標
IPC_RMID:刪除信號量,不需要第四個參數    
    
【2】.線程中信號量的使用步驟    
1.定義一個信號量
sem_t    sem;

2.初始化信號量    sem_init()函數
sem_init(&sem,0,1);
&sem:    定義的信號量的地址
第二個參數:0:表示同一進程中的線程間共享,非0:進程間共享
1:    信號量的初始值
注:sem處信號量爲同一進程中線程共享的信號量,
信號的初始值爲1,即P、V操作從1開始

3.獲取信號量(即P操作)
sem_wait(&sem);
&sem:定義的信號量的地址

ps:訪問共享資源

4.釋放信號量(即V操作)
sem_post(&sem);
&sem:定義的信號量的地址

5.銷燬信號量
sem_destroy(&sem);
&sem:定義的信號量的地址    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

                        
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章