国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Linux】進(jìn)程間通信——管道/共享內(nèi)存

這篇具有很好參考價值的文章主要介紹了【Linux】進(jìn)程間通信——管道/共享內(nèi)存。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1. 進(jìn)程間通信

進(jìn)程間通信(Inter-Process Communication,簡稱IPC)是指不同進(jìn)程之間進(jìn)行數(shù)據(jù)交換和共享信息的機(jī)制和技術(shù)。在操作系統(tǒng)中,每個進(jìn)程都是獨立運行的,有自己的地址空間和數(shù)據(jù),因此進(jìn)程之間需要一種機(jī)制來進(jìn)行通信,以便彼此協(xié)調(diào)工作、共享數(shù)據(jù)或者進(jìn)行同步操作。

進(jìn)程間通信的前提,也是重中之重,是讓不同的進(jìn)程看到同一份資源。 由于進(jìn)程的獨立性,只有先讓不同進(jìn)程看到同一份資源,有了通信的平臺,才能實現(xiàn)通信。本文重點在于如何搭建進(jìn)程間通信的平臺,使得不同進(jìn)程看到同一份資源。

2. 管道

管道,是一種傳統(tǒng)的進(jìn)程間通信方法。管道的本質(zhì)是一個特殊文件,一個進(jìn)程作為寫入端,一個進(jìn)程作為讀取段,通過寫入和讀取管道實現(xiàn)通信。

??管道分為匿名管道命名管道,它們的使用場景不同。

匿名管道

??匿名管道(pipe)應(yīng)用于有親緣關(guān)系的進(jìn)程之間通信(如:父子進(jìn)程、兄弟進(jìn)程)。以父子進(jìn)程為例,原理:

  1. 父進(jìn)程創(chuàng)建管道,并分別以寫方式和讀方式打開管道,此時父進(jìn)程就擁有了兩個新的文件描述符,以寫方式打開管道的文件描述符稱為寫端fd,以讀方式打開管道的文件描述符稱為讀端fd。

  2. 接著創(chuàng)建子進(jìn)程,子進(jìn)程繼承了父進(jìn)程的文件描述符表,二者有了相同的寫端fd和讀端fd。

  3. 然后根據(jù)需求關(guān)閉不要的文件描述符,如:父進(jìn)程寫數(shù)據(jù)給子進(jìn)程,即父進(jìn)程作為寫入端,子進(jìn)程作為讀取端,那就關(guān)閉父進(jìn)程的讀端fd和子進(jìn)程的寫端fd。

  4. 此時父子進(jìn)程已經(jīng)能看到同一份資源了,通信開始,父進(jìn)程調(diào)用write寫入管道,子進(jìn)程調(diào)用read讀取管道,和文件操作相同。

在這個過程中創(chuàng)建的管道,稱之為匿名管道。之所以是匿名管道,是因為整個過程中用戶都無法獲知管道的名稱等具體信息,該管道由OS維護(hù)。

?上述過程的邏輯演繹如下:

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

??補充

  • 管道是一種特殊的文件,它在內(nèi)存中以緩沖區(qū)的形式存在。因此打開管道就和打開文件一樣,OS也會在內(nèi)存中創(chuàng)建一個打開文件句柄來維護(hù)管道。通過打開文件句柄,我們可以引用到管道的緩沖區(qū),從而對其進(jìn)行讀寫操作。

  • 匿名管道的生命周期隨進(jìn)程。當(dāng)引用該管道的所有進(jìn)程退出,OS自動關(guān)閉并刪除匿名管道。(打開文件句柄和inode的引用計數(shù)問題)

  • 因為管道是一種臨時的通信機(jī)制,不像普通文件具有持久性的存儲需求,所以管道是沒有磁盤文件的。那么管道是否像文件一樣擁有一個inode呢?是的。管道文件的inode主要用于標(biāo)識和管理管道,記錄與管道相關(guān)的元數(shù)據(jù)信息,并跟蹤管道的引用計數(shù)。管道文件的inode并不鏈接實際數(shù)據(jù),數(shù)據(jù)是通過內(nèi)核的緩沖區(qū)進(jìn)行傳遞和管理的。

  • 管道是一種半雙工的通信方式,即一端寫一端讀,單向數(shù)據(jù)流動。

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

  • 下面是代碼分析。

??首先是創(chuàng)建匿名管道的接口

int pipe(int pipefd[2]);

pipe是一個系統(tǒng)調(diào)用接口。當(dāng)前進(jìn)程創(chuàng)建匿名管道,傳入?yún)?shù)pipefd是一個能夠存放2個元素的整型數(shù)組,調(diào)用成功后,管道的寫端fd和讀端fd存入pipefd中,pipefd[0]是讀端fd,pipefd[1]是寫端fd。

下面是pipe在2號手冊中的介紹。

NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is  set appropriately.

下面是使用匿名管道實現(xiàn)進(jìn)程間通信的一段代碼

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;
const int NUM = 1024;

// 先創(chuàng)建管道,進(jìn)而創(chuàng)建子進(jìn)程,父子進(jìn)程使用管道進(jìn)行通信
// 父進(jìn)程向管道當(dāng)中寫“i am father”,
// 子進(jìn)程從管道當(dāng)中讀出內(nèi)容, 并且打印到標(biāo)準(zhǔn)輸出

int main()
{
    // 1.創(chuàng)建管道
    int pipefd[2] = {0};
    int ret = pipe(pipefd);
    if (ret < 0)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        return 1;
    }

    // 2.創(chuàng)建子進(jìn)程
    pid_t id = fork();
    assert(id >= 0);

    if (id == 0)
    {
        // 子進(jìn)程讀
        // 3.關(guān)閉不要的fd
        close(pipefd[1]);

        // 4.通信
        char buf[NUM] = {0};
        int n = read(pipefd[0], buf, sizeof(buf) - 1);
        if (n > 0)
        {
            buf[n] = '\0';
            cout << buf << endl;
        }
        else if (n == 0)
        {
            cout << "讀取到文件末尾" << endl;
        }
        else
        {
            exit(1);
        }
        close(pipefd[0]);
        exit(0);
    }

    // 父進(jìn)程寫
    // 3.關(guān)閉不要的fd
    close(pipefd[0]);

    // 4.通信
    const char *msg = "I am father";
    write(pipefd[1], msg, strlen(msg));

    close(pipefd[1]);

    // 5.等待子進(jìn)程退出
    int n = waitpid(id, nullptr, 0);
    if (n == -1)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        return 1;
    }

    return 0;
}

?執(zhí)行結(jié)果

[ckf@VM-8-3-centos Testpipe]$ ./a.out 
I am father #子進(jìn)程成功讀取并輸出父進(jìn)程發(fā)送的信息

命名管道

??命名管道(named pipe)應(yīng)用于無親緣關(guān)系的進(jìn)程之間通信。無親緣關(guān)系的兩個進(jìn)程,無法通過繼承文件描述符表來獲得同一個匿名管道,因此就需要命名管道。命名管道有特定的文件名,多個進(jìn)程可以通過相同的文件名找到相同的管道,進(jìn)而實現(xiàn)通信。使用命名管道的步驟如下:

  1. 創(chuàng)建命名管道

    創(chuàng)建命名管道的方式有兩種,通過指令或系統(tǒng)調(diào)用。

    指令:

    mkfifo [選項] [name]
    OPTION:
    	-m MODE #設(shè)置管道的權(quán)限
    

    系統(tǒng)調(diào)用:

    NAME
           mkfifo - make a FIFO special file (a named pipe)
    
    SYNOPSIS
           #include <sys/types.h>
           #include <sys/stat.h>
    
           int mkfifo(const char *pathname, mode_t mode);
    RETURN VALUE
           On success mkfifo() returns 0.  In the case of an error, -1 is returned (in which case, errno is set appropriately).
    
  2. 進(jìn)程打開命名管道

    進(jìn)程可以調(diào)用open接口,以讀或?qū)懛绞酱蜷_命名管道,此時必須保證命名管道是存在的。注意:進(jìn)程要有命名管道對應(yīng)的權(quán)限才能正確地讀取或?qū)懭霐?shù)據(jù),權(quán)限在創(chuàng)建管道時設(shè)定。

  3. 通信

  4. 關(guān)閉管道,刪除管道

    進(jìn)程調(diào)用close關(guān)閉管道,退出程序。命名管道的生命周期不隨進(jìn)程,進(jìn)程退出命名管道依舊存在。因此需要用戶自行刪除,可以通過指令rm刪除命名管道文件,也可以在進(jìn)程中調(diào)用unlink接口。

    NAME
           unlink - delete a name and possibly the file it refers to
    
    SYNOPSIS
           #include <unistd.h>
    
           int unlink(const char *pathname);
    RETURN VALUE
           On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
    

??下面是兩個進(jìn)程使用命名管道實現(xiàn)進(jìn)程間通信,client是寫進(jìn)程,負(fù)責(zé)創(chuàng)建namedpipe和刪除namedpipe,并向server發(fā)送數(shù)據(jù),數(shù)據(jù)由用戶交互傳遞。server是讀進(jìn)程,只負(fù)責(zé)讀取client發(fā)送的數(shù)據(jù)。

注意: 對于打開命名管道的寫端,調(diào)用open時,若此時該命名管道沒有讀端,則寫端會阻塞等待至少一個讀端打開該管道,寫端才會打開。同理,若想打開讀端但是沒有寫端,也會阻塞等待。

//client
#include "common.hpp"

int main()
{
    // 1.創(chuàng)建命名管道
    umask(0);
    int ret = mkfifo(pipename.c_str(), 0666);
    if (ret < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }
    
    // 2.以寫方式打開命名管道
    int wfd = open(pipename.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }

    //3.向管道中寫入數(shù)據(jù)
    char buf[NUM] = {0};
    std::cout << "請輸入您想要發(fā)送給服務(wù)端的信息: " << std::endl;
    while (true)
    {
        char *str = fgets(buf, sizeof(buf), stdin);
        assert(str);
        (void)str;

        int n = strlen(buf);
        buf[n - 1] = '\0'; // 消除'\n'

        if (strcasecmp(buf, "quit") == 0)
            break;

        int ret = write(wfd, buf, sizeof(buf));
        assert(ret > 0);
        (void)ret;
    }

    // 4.退出,關(guān)閉寫端
    close(wfd);
    unlink(pipename.c_str());

    return 0;
}
//server
#include "common.hpp"

int main()
{
    // 1.以讀方式打開命名管道
    int rfd = open(pipename.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }

    //2.讀取管道中的數(shù)據(jù)
    char buf[NUM] = {0};
    while (true)
    {
        int cnt = read(rfd, buf, sizeof(buf));
        if (cnt > 0)
        {
            buf[cnt] = '\0';
            std::cout << "message from client: " << buf << std::endl;
        }
        else if (cnt == 0)
        {
            std::cout << "通信結(jié)束" << std::endl;
            break;
        }
        else
        {
            return 1;
        }
    }

    // 3.關(guān)閉讀端
    close(rfd);

    return 0;
}
//common.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>	
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <cassert>

const std::string pipename = "fifo";
const int NUM = 1024;

?實操演示

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)


管道的特性

??作為特殊的文件,管道具有一些特性(匿名管道和命名管道同時具備)

  1. 當(dāng)管道為空時或讀進(jìn)程讀完數(shù)據(jù)時,讀進(jìn)程再次讀取時會阻塞等待寫進(jìn)程寫入數(shù)據(jù)后才開始讀取。
  2. 當(dāng)管道為滿時,讀進(jìn)程沒有讀取數(shù)據(jù),寫進(jìn)程會阻塞等待讀進(jìn)程讀取出一些數(shù)據(jù)后再寫入數(shù)據(jù),否則未被讀取的數(shù)據(jù)可能會被覆蓋。
  3. 若所有寫進(jìn)程被關(guān)閉,讀進(jìn)程仍在讀取,此時讀進(jìn)程調(diào)用的read函數(shù)會返回0,表示讀取到文件末尾,即讀取結(jié)束
  4. 若所有讀進(jìn)程被關(guān)閉,寫進(jìn)程再寫入數(shù)據(jù)就無意義了,因此OS會發(fā)送信號SIGPIPE,終止寫進(jìn)程

??這種特性也被稱為“管道的阻塞機(jī)制”。管道的阻塞機(jī)制確保了數(shù)據(jù)在寫進(jìn)程和讀進(jìn)程之間的可靠傳遞和同步處理,提高了數(shù)據(jù)處理的準(zhǔn)確性和效率,為進(jìn)程之間的通信和數(shù)據(jù)交換提供了便利和可靠性。


管道的應(yīng)用:簡易的進(jìn)程池

使用匿名管道制作一個簡易的進(jìn)程池,大概思路:先創(chuàng)建一個父進(jìn)程,然后讓這個父進(jìn)程創(chuàng)建多個子進(jìn)程,通過用戶交互的模式,讓父進(jìn)程下發(fā)指定的任務(wù)給不同的子進(jìn)程。其中,”下發(fā)任務(wù)“這個過程,就是利用管道來實現(xiàn),父進(jìn)程對于每個子進(jìn)程都有唯一一個管道用以傳輸“任務(wù)”數(shù)據(jù)。

  1. 管理子進(jìn)程

    一個父進(jìn)程對多個子進(jìn)程,且每個子進(jìn)程對應(yīng)一個管道,那么肯定要先將多個子進(jìn)程管理起來。根據(jù)“先描述,再組織”的管理思想,我的設(shè)計如下:先將子進(jìn)程描述為一個結(jié)構(gòu)體,該結(jié)構(gòu)體中包含子進(jìn)程pid、子進(jìn)程對應(yīng)管道在父進(jìn)程中的寫端fd、以及一個子進(jìn)程名稱(自定義格式,為了后續(xù)方便調(diào)試觀察)。然后在父進(jìn)程中定義一個容器,用以組織這些創(chuàng)建出來的子進(jìn)程結(jié)構(gòu)體,方便后續(xù)管理。

    //描述子進(jìn)程結(jié)構(gòu)體
    struct ChildProc
    {
        ChildProc(int pid, int write_fd) : _pid(pid), _write_fd(write_fd)
        {
            _proc_name = "proc->" + to_string(_pid) + ":" + to_string(_write_fd);
        }
    
        int _pid;
        int _write_fd;
        string _proc_name;
    };
    
    //父進(jìn)程主函數(shù),即整個進(jìn)程池的框架
    int main()
    {
        //定義一個vector容器,用以組織ChildProc
        vector<ChildProc> child_processes;
    
        // 1.創(chuàng)建子進(jìn)程
        CreatProcess(child_processes);
    
        // 2.父進(jìn)程下發(fā)命令(用戶交互式)
        OrderProcess(child_processes);
    
        // 3.進(jìn)程退出
        WaitProcess(child_processes);
        cout << "子進(jìn)程已全部成功退出,并被回收!" << endl;
    
        return 0;
    }
    
  2. 創(chuàng)建子進(jìn)程

    父進(jìn)程循環(huán)創(chuàng)建子進(jìn)程。每次子進(jìn)程創(chuàng)建完畢后,由于父進(jìn)程尚且沒有向管道寫入數(shù)據(jù),當(dāng)前子進(jìn)程read阻塞等待,父進(jìn)程繼續(xù)創(chuàng)建下一個子進(jìn)程。父進(jìn)程每次fork創(chuàng)建完一個子進(jìn)程,要將其描述為ChildProc結(jié)構(gòu)體,再插入管理的容器中。

    const int child_process_num = 3;
    
    void CreatProcess(vector<ChildProc> &cps)
    {
        for (int i = 0; i < child_process_num; i++)
        {
            // 1.創(chuàng)建管道
            int pipefd[2] = {0};
            int ret = pipe(pipefd);
            if (ret < 0)
            {
                perror("The following error happen:");
            }
            
            // 父進(jìn)程寫,子進(jìn)程讀(父進(jìn)程向子進(jìn)程發(fā)送命令)
            
            // 2.創(chuàng)建子進(jìn)程,一個子進(jìn)程在父進(jìn)程中對應(yīng)一個寫端
            int id = fork();
            assert(id >= 0);
            
            // 子進(jìn)程
            if (id == 0)
            {
                // 3.關(guān)閉不要的fd
                close(pipefd[1]);
                
                // 子進(jìn)程接收并執(zhí)行命令
                while (true)
                {
                    int n = 0;
                    // 此時管道為空時,子進(jìn)程read阻塞等待父進(jìn)程下發(fā)命令
                    int cnt = read(pipefd[0], &n, sizeof(int));
                    if (cnt > 0)
                    {
                        //FuncArray在Tasks.hpp中實現(xiàn)
                        FuncArray[n]();
                        cout << endl;
                    }         
                    else if (cnt == 0)
                    {
                        //父進(jìn)程退出,即寫端關(guān)閉,read返回值為0,子進(jìn)程也隨之退出
                        cout << "讀取結(jié)束,子進(jìn)程退出"
                             << " pid: " << getpid() << endl;
                        break;
                    }
                    else
                    {
                        exit(1);
                    }
                }
                close(pipefd[0]);
                exit(0);
            }
    
            // 父進(jìn)程
            // 將子進(jìn)程(子進(jìn)程pid和寫端fd)管理起來,父進(jìn)程才方便下發(fā)命令
            cps.push_back(ChildProc(id, pipefd[1]));
            close(pipefd[0]);
        }
    }
    

    在common.hpp頭文件中,簡單寫幾個子進(jìn)程可執(zhí)行的任務(wù),這里沒有定義實際任務(wù),只是打印語句以表示任務(wù)成功執(zhí)行。后續(xù)這塊可完善。

    #pragma once
    #include <iostream>
    #include <functional>
    using namespace std;
    
    void TaskWeChat()
    {
        cout << "wechat is running..." << endl;
    }
    
    void TaskChrome()
    {
        cout << "chrome is running..." << endl;
    }
    
    void TaskSteam()
    {
        cout << "steam is running.." << endl;
    }
    
    const function<void()> FuncArray[] = {TaskWeChat,TaskChrome,TaskSteam};
    
  3. 父進(jìn)程下發(fā)命令給子進(jìn)程

    int SelectBoard()
    {
        //用戶選擇面板
        cout << "#########################" << endl;
        cout << "# 0.wechat     1.chrome #" << endl;
        cout << "# 2.steam      3.quit   #" << endl;
        cout << "#########################" << endl;
        cout << "請選擇你將下發(fā)的命令: ";
    
        int command = 0;
        cin >> command;
        return command;
    }
    
    void OrderProcess(vector<ChildProc> &cps)
    {
        int num = -1;
        while (true)
        {
            // 用戶交互, 下發(fā)命令
            int command = SelectBoard();
            if (command == 3)
                break;
            if (command < 0 || command > 2)
                continue;
    
            // 輪詢調(diào)用子進(jìn)程
            num = (num + 1) % cps.size();
            printf("調(diào)用了子進(jìn)程%d號, ", num);
            cout << cps[num]._proc_name << endl;
            
            // 將命令寫入對應(yīng)子進(jìn)程的管道中
            write(cps[num]._write_fd, &command, sizeof(command));
            sleep(1);
        }
    }
    
  4. 等待子進(jìn)程進(jìn)程退出并回收

    void WaitProcess(vector<ChildProc> &cps)
    {
        // 先關(guān)閉父進(jìn)程的所有寫端,根據(jù)管道的特性(關(guān)閉管道所有寫端,讀端退出),關(guān)閉寫端讓對應(yīng)的子進(jìn)程退出
        // 隨后,父進(jìn)程要回收所有的子進(jìn)程
    
        for (auto &cp : cps)
        {
            close(cp._write_fd);
            waitpid(cp._pid, nullptr, 0);
        }
    }
    

?運行程序,并進(jìn)行測試。發(fā)現(xiàn)讓父進(jìn)程發(fā)送0、1、2命令都正常,可當(dāng)發(fā)送3號退出命令,讓父進(jìn)程等待并回收子進(jìn)程時,程序卡住了。

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

這里有一個隱藏的bug。匿名管道,我們運用了子進(jìn)程繼承父進(jìn)程文件描述符表的機(jī)制,但在進(jìn)程池中,由于利用了這個繼承機(jī)制,又會產(chǎn)生bug。父進(jìn)程創(chuàng)建0號子進(jìn)程時是沒問題的,如我們預(yù)期。當(dāng)創(chuàng)建1號子進(jìn)程時,由于此時父進(jìn)程文件描述符表有了0號子進(jìn)程的寫端fd,被1號子進(jìn)程繼承了,所以此時0號子進(jìn)程的管道有了兩個寫端fd,這并不符合我們的預(yù)期,我們的設(shè)計是讓父進(jìn)程和每個子進(jìn)程之間有一個獨立的管道。若創(chuàng)建三個子進(jìn)程,最后進(jìn)程池的結(jié)構(gòu)如下:

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

再看看我們剛才寫的WaitProcess函數(shù)。造成阻塞的原因是:close關(guān)閉第一個子進(jìn)程管道的寫端時,并沒有關(guān)閉全部寫端,因此該子進(jìn)程并沒有退出,waitpid阻塞等待。

void WaitProcess(vector<ChildProc> &cps)
{
    for (auto &cp : cps)
    {
        close(cp._write_fd);
        waitpid(cp._pid, nullptr, 0);
    }
}

??解決方法:

  1. 因為最后一個子進(jìn)程只有父進(jìn)程一個寫端,因此可以先關(guān)閉最后一個子進(jìn)程的寫端fd,此時該子進(jìn)程成功退出,OS自動關(guān)閉其所有文件描述符,因此它由于bug鏈接到其它子進(jìn)程的管道上的寫端fd會被關(guān)閉。如此逆向close即可完成。

  2. 這種進(jìn)程池結(jié)構(gòu)并不是我們想要的,因此直接在創(chuàng)建子進(jìn)程時關(guān)閉對應(yīng)管道錯誤的寫端fd,形成我們期望的進(jìn)程池結(jié)構(gòu),才是上策。修改代碼如下:

    void CreatProcess(vector<ChildProc> &cps)
    {
        //創(chuàng)建一個容器wfds,用以存放父進(jìn)程創(chuàng)建一個子進(jìn)程時,已經(jīng)擁有的寫端fd
        vector<int> wfds;
        for (int i = 0; i < child_process_num; i++)
        {
            int pipefd[2] = {0};
            int ret = pipe(pipefd);
            if (ret < 0)
            {
                perror("The following error happen:");
            }
    
            // 每次創(chuàng)建管道后,將寫端fd存入wfds
            wfds.push_back(pipefd[1]);
            
            int id = fork();
            assert(id >= 0);
    
            if (id == 0)
            {
                // 子進(jìn)程關(guān)閉從父進(jìn)程繼承的所有寫端(包括子進(jìn)程自己管道的和其它管道的寫端fd)!!         
                for (auto &wfd : wfds)
                {
                    close(wfd);
                }
                
                // 錯誤寫法,在當(dāng)前子進(jìn)程push寫端fd,其它子進(jìn)程看不到?。?!寫時拷貝問題
                // wfds.push_back(pipefd[1]);
                // for (auto& wfd : wfds)
                // {
                //     close(wfd);
                //     cout << "關(guān)閉fd: " << wfd << endl;
                // }
    
    
                while (true)
                {
                    int n = 0;
                    int cnt = read(pipefd[0], &n, sizeof(int));
                    if (cnt > 0)
                    {              
                        FuncArray[n]();
                        cout << endl;
                    }
                    else if (cnt == 0)
                    {
                        cout << "讀取結(jié)束,子進(jìn)程退出"
                             << " pid: " << getpid() << endl;
                        break;
                    }
                    else
                    {
                        exit(1);
                    }
                }
                close(pipefd[0]);
                exit(0);
            }
            
            cps.push_back(ChildProc(id, pipefd[1]));
            close(pipefd[0]);
        }
    }
    

    此時再次發(fā)送quit指令,觀察到子進(jìn)程成功退出并被父進(jìn)程回收。

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)


3. System V共享內(nèi)存

另一種進(jìn)程間通信的方式是共享內(nèi)存。共享內(nèi)存是最快的進(jìn)程間通信(IPC)形式。因為其通信過程中,傳輸數(shù)據(jù)時,不再需要經(jīng)過內(nèi)核的“中轉(zhuǎn)”,而是直接通過地址的映射獲得共享資源。

共享內(nèi)存的概念

??在進(jìn)程間通信(IPC)中,共享內(nèi)存是一種特殊的通信機(jī)制,允許多個進(jìn)程共享同一塊物理內(nèi)存區(qū)域,從而實現(xiàn)高效的數(shù)據(jù)交換和共享。與其他IPC方式相比,共享內(nèi)存的主要優(yōu)勢是數(shù)據(jù)直接存儲在內(nèi)存中,避免了數(shù)據(jù)在進(jìn)程之間的復(fù)制,從而提高了通信的速度和效率。缺點是無法保證數(shù)據(jù)的安全性。

共享內(nèi)存的結(jié)構(gòu)

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

共享內(nèi)存(Shared Memory Segment,簡稱shm),是一段由多個進(jìn)程共享的物理內(nèi)存空間,各個進(jìn)程將其通過頁表映射到自己的地址空間共享區(qū)中。使得多個進(jìn)程可以訪問相同的空間,實現(xiàn)交換數(shù)據(jù),完成IPC。圖中,struct_shm(在真正的內(nèi)核中并非這個名字)是內(nèi)核中用于管理共享內(nèi)存的一個結(jié)構(gòu)體,每個共享內(nèi)存對應(yīng)一個該結(jié)構(gòu)體,該結(jié)構(gòu)體中包含了共享內(nèi)存區(qū)的各種屬性和元數(shù)據(jù),如共享內(nèi)存的大小、權(quán)限、關(guān)聯(lián)進(jìn)程等信息,這些結(jié)構(gòu)體也會被OS組織并管理起來。

共享內(nèi)存 = 管理共享內(nèi)存信息的數(shù)據(jù)結(jié)構(gòu) + 真正的共享內(nèi)存空間

共享內(nèi)存的使用

??以下假設(shè)使用共享內(nèi)存通信的只有兩個進(jìn)程,實際上一個共享內(nèi)存可以連接多個進(jìn)程。

  1. 共享內(nèi)存的獲取

    通信雙方,必須先能看到同一份共享資源,才能進(jìn)行通信。獲取的方式是,一方負(fù)責(zé)創(chuàng)建共享內(nèi)存,另一方查找對方創(chuàng)建的共享內(nèi)存,用到的接口是shmget。

    NAME
           shmget - allocates a System V shared memory segment
    
    SYNOPSIS
           #include <sys/ipc.h>
           #include <sys/shm.h>
    
           int shmget(key_t key, size_t size, int shmflg);
    
    RETURN VALUE
           On success, a valid shared memory identifier is returned.  
           On error, -1 is returned, and errno is set to indicate the error.
    

    ??參數(shù)

    • key

      用于標(biāo)識唯一的一個共享內(nèi)存段。多個進(jìn)程約定同一個key,可獲取同一份共享內(nèi)存。key是一個整型,可以通過ftok函數(shù)獲取

      key_t ftok(const char *pathname, int proj_id);
      

      ftok的參數(shù)是一個路徑字符串pathname和一個整型值項目idproj_id。內(nèi)含特定的算法,通過這兩個參數(shù)生成一個重復(fù)率較低的key值,并作為返回值。只要參數(shù)相同,生成的key值就相同。

    • size

      共享內(nèi)存的大小,單位是字節(jié)byte

    • shmflg

      標(biāo)記位。主要的標(biāo)記有IPC_CREATIPC_EXCL,若shmflg==IPC_CREAT,表示若以key為鍵值的共享內(nèi)存不存在,創(chuàng)建之。若存在,用之即可。若shmflg==IPC_CREAT|IPC_EXCL,表示若以key為鍵值的共享內(nèi)存不存在,創(chuàng)建之。若存在,報錯。(IPC_EXCL不能單獨使用,只與IPC_CREAT一起使用)。另外,標(biāo)記位還包含mode_flags,它用于定義共享內(nèi)存的權(quán)限,格式與open的參數(shù)mode相同 ,指明onwer、group、world(運行進(jìn)程者)對于共享內(nèi)存的權(quán)限。

    ??返回值

    ? 共享內(nèi)存描述符(shared memory identifier,簡稱shmid),用于標(biāo)識唯一的一段共享內(nèi)存。

    ??參數(shù)key和返回值shmid的區(qū)別?

    key在函數(shù)調(diào)用時使用,意味著共享內(nèi)存可能尚未存在。key的作用是在進(jìn)程獲取共享內(nèi)存之前(此時共享內(nèi)存可能還沒創(chuàng)建),唯一標(biāo)識一個共享內(nèi)存段,使通信雙方能夠約定同一個共享內(nèi)存段。這樣,一個進(jìn)程創(chuàng)建以key為鍵值的shm,另一個進(jìn)程查找以key為鍵值的shm,并獲取相同的shmid。shmid用于進(jìn)程獲取共享內(nèi)存后,唯一標(biāo)識一個共享內(nèi)存段,這個標(biāo)識符可以用于后續(xù)的共享內(nèi)存操作 。

    二者作用大致相同,但作用的時間節(jié)點不同。

  2. 進(jìn)程與共享內(nèi)存建立聯(lián)系

    上一步做的事,只是讓通信雙方獲知了用哪一塊共享內(nèi)存(獲取相同的shmid),但并沒有真正與共享內(nèi)存建立聯(lián)系。那么現(xiàn)在就要把進(jìn)程和共享內(nèi)存鏈接起來,即在各自的地址空間中映射共享內(nèi)存段。需要用到的接口是shmat。(shm attach)

    SYNOPSIS
           #include <sys/types.h>
           #include <sys/shm.h>
    
           void *shmat(int shmid, const void *shmaddr, int shmflg);
    RETURN VALUE
           On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate  the  cause  of  the error.
    

    ??參數(shù)

    • shmid

      就是第一步中獲得的shmid。

    • shmaddr

      指定共享內(nèi)存映射到當(dāng)前進(jìn)程的地址。一般設(shè)置為NULL,由OS自動選擇映射的地址,較為安全可靠。

    • shmflg

      指明鏈接共享內(nèi)存的讀寫模式。設(shè)置SHM_RDONLY為只讀, 否則是即讀又寫(一般設(shè)置為0)。沒有只寫的選項。注意,進(jìn)程必須有對應(yīng)權(quán)限才能設(shè)定對應(yīng)的shmflg,如:設(shè)置SHM_RDONLY,進(jìn)程對該共享內(nèi)存必須有讀權(quán)限。設(shè)置為0,進(jìn)程對該共享內(nèi)存必須有讀權(quán)限和寫權(quán)限。權(quán)限在shmget函數(shù)中設(shè)定。

    ??返回值

    ? 一個void*類型的指針,指向當(dāng)前進(jìn)程地址空間中映射共享內(nèi)存段的起始地址,后續(xù)該地址為shmaddr。

  3. 開始通信,交換數(shù)據(jù)

    不像管道需要調(diào)用系統(tǒng)接口寫入和讀取數(shù)據(jù),共享內(nèi)存只需要在映射的地址空間中讀寫數(shù)據(jù),這段空間的起始地址在第二步已經(jīng)獲得,直接當(dāng)成數(shù)組的起始地址用就行。注意,獲得的指針shmaddr是void*類型,不同場景下可能需要強(qiáng)轉(zhuǎn)成其它類型來使用。

  4. 進(jìn)程與共享內(nèi)存解除聯(lián)系

    通信結(jié)束后,通信雙方無需再引用共享內(nèi)存,即可先解除與共享內(nèi)存的聯(lián)系。因為一個共享內(nèi)存可能會被多對進(jìn)程引用,而不止一個,所以只有當(dāng)引用該共享內(nèi)存的進(jìn)程數(shù)量為0時,才會刪除這個共享內(nèi)存。解除進(jìn)程與共享內(nèi)存的聯(lián)系,用到接口shmdtshm detach

    SYNOPSIS
           #include <sys/types.h>
           #include <sys/shm.h>
    
           int shmdt(const void *shmaddr);
    RETURN VALUE
           On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
    

    傳入shmaddr即可,返回值無意義,只是用作判斷函數(shù)調(diào)用成功與否。

  5. 刪除共享內(nèi)存

    NAME
           shmctl - System V shared memory control
    
    SYNOPSIS
           #include <sys/ipc.h>
           #include <sys/shm.h>
    
           int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    

    ??參數(shù)

    • shmid

      要刪除的共享內(nèi)存描述符

    • cmd

      控制指令。刪除的指令是IPC_RMID。

    • buf

      用于接收其它指令的返回值。刪除時傳入NULL即可。

?注意:進(jìn)程間通信時,創(chuàng)建和刪除共享內(nèi)存的工作最好由一個進(jìn)程來完成,其它進(jìn)程只是與已創(chuàng)建的共享內(nèi)存進(jìn)行連接和斷連即可。

除了系統(tǒng)調(diào)用,還有一些關(guān)于共享內(nèi)存的指令:

ipcs -m #查看共享內(nèi)存信息

【Linux】進(jìn)程間通信——管道/共享內(nèi)存,Linux,linux,服務(wù)器,操作系統(tǒng)

ipcrm [OPTION] [...] #刪除共享內(nèi)存
OPTION:
  -M 按key刪除
  -m 按shmid刪除

代碼實現(xiàn)

由于利用共享內(nèi)存實現(xiàn)IPC時,總是有相似的前置工作(創(chuàng)建和連接)和后置工作(斷連和刪除),因此可以將其封裝在一個類中,將前置工作封裝在類的構(gòu)造函數(shù)中,后置工作封裝在類的析構(gòu)函數(shù)中,實現(xiàn)共享內(nèi)存自動化搭建和銷毀。如下代碼:

//頭文件common.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <stdlib.h>
#include <cassert>

const std::string pathname = ".";
const int proj_id = 666;
const int shm_size = 4096;

#define CREATER 0
#define USER 1

class smart_init
{
public:
    smart_init(int type)
    {
        // 獲取共享內(nèi)存
        assert(type == CREATER || type == USER);
        if (type == CREATER)
            _shmid = creatShm(getKey());
        else if (type == USER)
            _shmid = searchShm(getKey());

        _type = type;

        // 與共享內(nèi)存建立聯(lián)系
        _shm_addr = attachShm(_shmid);
    }

    ~smart_init()
    {
        // 與共享內(nèi)存斷開聯(lián)系
        detachShm(_shm_addr);

        if (_type == CREATER)
        {
            remoteShm(_shmid);
        }
    }

    void *get_shmaddr()
    {
        return _shm_addr;
    }

private:
    key_t getKey();
    int creatShm(key_t k);
    int searchShm(key_t k);
    int getShm(key_t k, int flag);
    void *attachShm(int shmid);
    void detachShm(const void *shmaddr);
    void remoteShm(int shmid);

private:
    int _type;
    int _shmid;
    void *_shm_addr;
};

std::string toHex(int n)
{
    char buf[64] = {0};
    snprintf(buf, sizeof(buf), "0x%x", n);
    return std::string(buf);
}

key_t smart_init::getKey()
{
    key_t k = ftok(pathname.c_str(), proj_id);
    if (k == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int smart_init::getShm(key_t k, int flag)
{
    int shmid = shmget(k, shm_size, flag);
    if (shmid == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
    return shmid;
}

int smart_init::creatShm(key_t k)
{
    umask(0);
    return getShm(k, IPC_CREAT | IPC_EXCL | 0666);
}

int smart_init::searchShm(key_t k)    
{
    umask(0);
    return getShm(k, 0666);
}

void *smart_init::attachShm(int shmid)
{
    void *shm_ptr = shmat(shmid, nullptr, 0);
    if (shm_ptr == (void *)-1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(3);
    }
    return shm_ptr;
}

void smart_init::detachShm(const void *shmaddr)
{
    int ret = shmdt(shmaddr);
    if (ret == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(4);
    }
}

void smart_init::remoteShm(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, nullptr);
    if (ret == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(5);
    }
}
//進(jìn)程A
#include "common.hpp"

int main()
{
    smart_init si(CREATER);
    char* shm_ptr = (char*)si.get_shmaddr();
    
    //通信
    int cnt = 0;
    const char* msg = "i am process A";
    strcpy(shm_ptr,msg);
    sleep(10);

    return 0;
}
//進(jìn)程B
#include "common.hpp"

int main()
{
    smart_init si(USER);

    //通信
    char* shm_ptr = (char*)si.get_shmaddr();
    printf("message from A: %s\n",shm_ptr);

    return 0;
}

ENDING…文章來源地址http://www.zghlxwxcb.cn/news/detail-591065.html

到了這里,關(guān)于【Linux】進(jìn)程間通信——管道/共享內(nèi)存的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【Linux】進(jìn)程間通信之共享內(nèi)存

    【Linux】進(jìn)程間通信之共享內(nèi)存

    共享內(nèi)存比管道快哦~ 文章目錄 前言 一、共享內(nèi)存的實現(xiàn)原理 二、實現(xiàn)共享內(nèi)存的代碼 總結(jié) 共享內(nèi)存區(qū)是最快的IPC形式。一旦這樣的內(nèi)存映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)傳遞不再涉及到內(nèi)核,換句話說是進(jìn)程不再通過執(zhí)行進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來傳遞彼此的

    2024年02月03日
    瀏覽(22)
  • Linux 共享內(nèi)存mmap,進(jìn)程通信

    Linux 共享內(nèi)存mmap,進(jìn)程通信

    進(jìn)程間通信是操作系統(tǒng)中重要的概念之一,使得不同的進(jìn)程可以相互交換數(shù)據(jù)和進(jìn)行協(xié)作。其中,共享內(nèi)存是一種高效的進(jìn)程間通信機(jī)制,而內(nèi)存映射(mmap)是實現(xiàn)共享內(nèi)存的一種常見方法。 存儲映射 I/O 是 一個磁盤文件 與 存儲空間中的一個緩沖區(qū)相映射 。于是, 當(dāng)從緩

    2024年02月13日
    瀏覽(26)
  • 進(jìn)程間通信--共享內(nèi)存詳解【Linux】

    進(jìn)程間通信--共享內(nèi)存詳解【Linux】

    本文詳細(xì)講解了共享內(nèi)存的原理和使用,并且通過實例代碼角度來深度理解共享內(nèi)存,下面就讓我們開始吧。 數(shù)據(jù)傳輸:一個進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個進(jìn)程 資源共享:多個進(jìn)程之間共享同樣的資源。 通知事件:一個進(jìn)程需要向另一個或一組進(jìn)程發(fā)送消息,通知它(

    2024年02月02日
    瀏覽(26)
  • (26)Linux 進(jìn)程通信之共享內(nèi)存(共享儲存空間)

    (26)Linux 進(jìn)程通信之共享內(nèi)存(共享儲存空間)

    共享內(nèi)存是System V版本的最后一個進(jìn)程間通信方式。 共享內(nèi)存,顧名思義就是允許兩個不相關(guān)的進(jìn)程訪問同一個邏輯內(nèi)存,共享內(nèi)存是兩個正在運行的進(jìn)程之間共享和傳遞數(shù)據(jù)的一種非常有效的方式。不同進(jìn)程之間共享的內(nèi)存通常為同一段物理內(nèi)存。進(jìn)程可以將同一段物理內(nèi)

    2024年01月16日
    瀏覽(24)
  • 【hello Linux】進(jìn)程間通信——共享內(nèi)存

    【hello Linux】進(jìn)程間通信——共享內(nèi)存

    目錄 前言: 1. System V共享內(nèi)存 1. 共享內(nèi)存的理解 2. 共享內(nèi)存的使用步驟 3. 共享內(nèi)存的使用 ????????1. 共享內(nèi)存的創(chuàng)建 ????????查看共享內(nèi)存 ????????2. 共享內(nèi)存的釋放 ????????3. 共享內(nèi)存的掛接 ????????4. 共享內(nèi)存的去掛接 4. 共享內(nèi)存的使用示例 1. 兩進(jìn)

    2024年02月01日
    瀏覽(34)
  • Linux--進(jìn)程間的通信-共享內(nèi)存

    Linux--進(jìn)程間的通信-共享內(nèi)存

    前文: Linux–進(jìn)程間的通信-匿名管道 Linux–進(jìn)程間的通信–進(jìn)程池 Linux–進(jìn)程間的通信-命名管道 對于兩個進(jìn)程,通過在內(nèi)存開辟一塊空間(操作系統(tǒng)開辟的),進(jìn)程的虛擬地址通過頁表映射到對應(yīng)的共享內(nèi)存空間中,進(jìn)而實現(xiàn)通信 ; 特點和作用: 高效性: 共享內(nèi)存是一種

    2024年04月26日
    瀏覽(32)
  • 【Linux】進(jìn)程間的通信之共享內(nèi)存

    【Linux】進(jìn)程間的通信之共享內(nèi)存

    利用 內(nèi)存共享 進(jìn)行進(jìn)程間的通信的原理其實分為以下幾個步驟: 在物理內(nèi)存中創(chuàng)建一塊共享內(nèi)存。 將共享內(nèi)存鏈接到要通信的進(jìn)程的頁表中,并通過頁表進(jìn)行進(jìn)程地址空間的映射。 進(jìn)程地址空間映射完畢以后返回首個虛擬地址,以便于進(jìn)程之間進(jìn)行通信。 根據(jù)共享內(nèi)存的

    2024年02月09日
    瀏覽(36)
  • 【Linux】進(jìn)程間通信——system V共享內(nèi)存

    【Linux】進(jìn)程間通信——system V共享內(nèi)存

    目錄 ?寫在前面的話 System V共享內(nèi)存原理 System V共享內(nèi)存的建立 代碼實現(xiàn)System V共享內(nèi)存 創(chuàng)建共享內(nèi)存shmget() ftok() 刪除共享內(nèi)存shmctl() 掛接共享內(nèi)存shmat() 取消掛接共享內(nèi)存shmdt() 整體通信流程的實現(xiàn) ???????? 上一章我們講了進(jìn)程間通信的第一種方式 --- 管道,這一章我

    2024年02月14日
    瀏覽(29)
  • 【Linux】進(jìn)程間通信 -- system V共享內(nèi)存

    【Linux】進(jìn)程間通信 -- system V共享內(nèi)存

    共享內(nèi)存區(qū)是最快的IPC形式。一旦這樣的內(nèi)存映射到共享它的進(jìn)程的地址空間,這些進(jìn)程間數(shù)據(jù)傳遞不再涉及到內(nèi)核,換句話說是進(jìn)程不再通過執(zhí)行進(jìn)入內(nèi)核的系統(tǒng)調(diào)用來傳遞彼此的數(shù)據(jù) 理解: 進(jìn)程間通信,是專門設(shè)計的,用來IPC 共享內(nèi)存是一種通信方式,所有想通信的進(jìn)程

    2024年02月16日
    瀏覽(29)
  • Linux學(xué)習(xí)記錄——?? 進(jìn)程間通信(2)共享內(nèi)存

    Linux學(xué)習(xí)記錄——?? 進(jìn)程間通信(2)共享內(nèi)存

    system是一套標(biāo)準(zhǔn),獨立于文件系統(tǒng)之外,是系統(tǒng)專門為通信而設(shè)計出來的內(nèi)核模塊,稱之為system V的IPC通信機(jī)制。 共享內(nèi)存的主要做法也是讓兩個毫不相關(guān)的進(jìn)程看到同一份資源。 進(jìn)程的地址空間內(nèi),棧和堆區(qū)之間有個共享區(qū),堆是向上增長,棧是向下增長,重合的那個地方

    2023年04月24日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包