【操作系统】进程间的通信

简介: 【操作系统】进程间的通信

进程

程序是计算机指令的集合,它以文件的形式存储在磁盘上。

进程通常上被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动,一个程序可以对应多个进程。

进程是资源申请,高度和独立运行的单位,因此,它使用系统中的运行资源,而程序不能申请使用系统资源,不能被系统高度也不能作为独立运行的单位,因此它不占系统运行资源。

进程的组成

  • 操作系统用来管理进行的内核对象

内核对象也是系统用来存放关于进程的统计信息的地方,内核对象是操作系统内部分配的一个内在块,该内存块是一种数据结构,其成员负责维护该对象的各种信息。

  • 地址空间

它包含所有可执行模块或DLL模块的代码和数据,另外,它也包含动态内存分配的地址空间,例如线程的栈和堆分配空间。

  • 进程从来不执行任何东西,它只是纯粹的容器,或说是线程的执行环境。

若要使它完成某项操作,它必须拥有一个在它环境中运行的的线程,次线程负责执行包含在进程的地址空间中的代码,也就是,真正完成代码执行的线程。

子进程

子进程还是一个进程,指的是由另一个进程(对应称之为父进程)所创建的进程。

单任务的同步机制——线程、子进程都可以实现。

需要保护地址空间。

子进程的线程既可以在父进程终止之后执行代码,也可以在父进程运行的过程中执行代码。

创建进程

CreateProcessW

CreateProcess函数
CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,// 该字符串可以指定要执行的模块的完整路径和文件名
    _Inout_opt_ LPWSTR lpCommandLine,  //命令行

    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
//该 结构确定子进程是否可以继承返回到新进程对象的句柄。如果//lpProcessAttributes为NULL,则不能继承该句柄
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
//该结构确定子进程是否可以继承返回到新线程对象的句柄。如果//lpThreadAttributes为NULL,则不能继承该句柄
    _In_ BOOL bInheritHandles,
//如果此参数为TRUE,则新进程将继承调用进程中的每个可继承句柄。如果参//数为FALSE,则不会继承句柄。请注意,继承的句柄与原始句柄具有相同的值和//访问权限
    _In_ DWORD dwCreationFlags,// 控制优先级类别和流程创建的标志 CREATE_NEW_CONSOLE
    _In_opt_ LPVOID lpEnvironment,// 指向新进程的环境块的指针。如果此参数为//NULL,则新进程将使用调用进程的环境
//
    _In_opt_ LPCWSTR lpCurrentDirectory,// 进程当前目录的完整路径
    _In_ LPSTARTUPINFOW lpStartupInfo, //设置扩展属性
    _Out_ LPPROCESS_INFORMATION lpProcessInformation // 该结构接收有关新进程的标识//信息
    );

示例:创建一个用firefox打开bing的进程。

#include<stdio.h>
#include<tchar.h>
#include<windows.h>
void RunExe()
{
    //TCHAR szApplicationName[] = _T("D:\\Mozilla Firefox\\firefox.exe");
    TCHAR szlpCommandLine[] = _T("\"D:\\Mozilla Firefox\\firefox.exe\"https://cn.bing.com/");
    STARTUPINFO strStartupInfo;
    memset(&strStartupInfo,0,sizeof(strStartupInfo));
    strStartupInfo.cb = sizeof(strStartupInfo);
    PROCESS_INFORMATION szProcessInformation;
    memset(&szProcessInformation,0,sizeof(szProcessInformation));
    int iRet = CreateProcess(NULL, szlpCommandLine, NULL, NULL, false,CREATE_NEW_CONSOLE,NULL,NULL,&strStartupInfo,&szProcessInformation);

    if (iRet)
    {
        //创建成功
        WaitForSingleObject(szProcessInformation.hProcess,3000);
        CloseHandle(szProcessInformation.hProcess);
        CloseHandle(szProcessInformation.hThread);
        szProcessInformation.dwProcessId = 0;
        szProcessInformation.dwThreadId = 0;
        szProcessInformation.hThread = 0;
        szProcessInformation.hProcess = 0;
        printf_s("Success iRet = %d\n",iRet);

    }
    else
    {
        printf_s("Create Failed iRet = %d,errorcode =%d\n",iRet,GetLastError());
    }

}
int main(void)
{

    printf("test\n");
    RunExe();
    system("pause");
    return 0;
}

进程间的通信方式

  1. socket编程——IP和端口
  2. 剪贴板——剪贴板的内核对象
  3. 邮槽——邮槽的内核对象
  4. 匿名管道——内核对象
  5. 命名管道——内核对象
  6. Copy_data findwindows wm_copydata——消息sendmessage

剪贴板

系统维护管理的一块内存区域。

原理:当一个进程在复制数据时,是将数据放到内存区域中,当另一个进程在粘贴数据时,从该内存区域取出数据,显示到窗口上面。

示例:

void CClipboardDlg::OnBnClickedButton2()//发送
{
    // 1 打开剪切板
    if (OpenClipboard())
    {
        //2 清空剪切板
        EmptyClipboard();
        char* szSendBuf;
        //3 获取编辑框的内容
        CStringW strSendW;
        GetDlgItemText(IDC_EDIT_SEND, strSendW);

        CStringA strSend = (CStringA)strSendW;

        //4 分配一个内存对象,内存对象的句柄就是hClip
        HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength() + 1);
        //5 将剪切板句柄加锁
        szSendBuf = (char*)GlobalLock(hClip);
        strcpy(szSendBuf, strSend);
        TRACE("szSendBuf = %s", szSendBuf);
        GlobalUnlock(hClip);
        //6 将数据放入剪切板
        SetClipboardData(CF_TEXT, hClip);
        //关闭剪切板
        CloseClipboard();
    }

}


void CClipboardDlg::OnBnClickedButton3()//接收
{
    if (OpenClipboard())
    {
        //确认剪切板是否可用
        if (IsClipboardFormatAvailable(CF_TEXT))
        {
            HANDLE hClip;
            char* pBuf;
            //向剪切板要数据
            hClip = GetClipboardData(CF_TEXT);
            pBuf = (char*)GlobalLock(hClip);
            USES_CONVERSION;
            LPCWSTR strBuf = A2W(pBuf);
            GlobalUnlock(hClip);
            SetDlgItemText(IDC_EDIT_RECV, strBuf);
        }
        CloseClipboard();
    }

}

image-20220103195035740

邮槽

使用邮槽通信的进程分为服务端和客户端。邮槽有服务端创建,在创建时需要指定邮槽名,创建之后服务端得到邮槽的句柄 。在邮槽创建后,客户端可以通过邮槽名的打开邮槽,在获得句柄后可以向邮槽写入消息。

邮槽通信是单向的,只有服务端才能从邮槽中读取消息,客户端只能写入消息。消息是先入先出的。客户端先写入的消息在服务端先被读取。

通过邮槽通信的数据可以是任意格式的,但是一条消息不能大于424字节。

邮槽除了在本机内进程进程间通信外,在主机间也可以通信。在主机间进程邮槽通信时,数据通过网络传播时使用的是数据包协议(UDP),所以是一种不可靠通信。通过网络进程邮槽通信时,客户端必须知道服务端的主机名或域名。


示例:

服务端

void CChildView::OnSlot()
{

    //    "\\\\.\\mailslot\\Mymailslot    \\.\mailslot\Mymailslot 
    //  1  创建一个邮槽
    LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
    HANDLE hSlot = CreateMailslot(szSlotName,
        0,                             // no maximum message size 
        MAILSLOT_WAIT_FOREVER,         // no time-out for operations 
        NULL); // default security

    if (hSlot == INVALID_HANDLE_VALUE)
    {
        TRACE("CreateMailslot failed with %d\n", GetLastError());
        return;
    }
    // 2 读取数据
    char szBuf[100] = { 0 };
    DWORD dwRead;
    TRACE("Begin ReadFile");
    if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL))
    {
        MessageBox(_T("读取数据失败"));
        CloseHandle(hSlot);
        return;
    }
    TRACE("End ReadFile");
    MessageBox((CStringW)szBuf);
    CloseHandle(hSlot);
     
}

客户端

void CChildView::OnSend()
{
    //创建一个文件句柄
    LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
    HANDLE    hMailSlot = CreateFile(szSlotName,FILE_GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (hMailSlot == INVALID_HANDLE_VALUE)
    {
        TRACE("CreateMail fail with %d\n",GetLastError());
        return;
    }
    //写入数据
    char szBuf[] = "ZYX is handsome";
    DWORD dwWrite;
    if (!WriteFile(hMailSlot,szBuf,strlen(szBuf)+1,&dwWrite,NULL))
    {
        MessageBox(_T("写入数据失败"));
        CloseHandle(hMailSlot);
        return;
    }
    CloseHandle(hMailSlot);
}

匿名管道

匿名管道是一个没有命名的单向管道,本质上就是一个共享的内存,抽象成是管道。

通常用来在父进程和子进程之间通信。只能实现本地两个进程之间的通信。不能实现网络通信。

优点是效率高,原理本质上就是共享内存。

CreatePipe

CreatePipe(
    _Out_ PHANDLE hReadPipe,  //该变量接收管道的读取句柄
    _Out_ PHANDLE hWritePipe,// 该变量接收管道的写句柄
    _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,//安全属性NULL-句柄是否能被子进程继承
    _In_ DWORD nSize  //管道缓冲区的大小 0 :默认缓冲区大小
);

命名管道

与Socket相似,支持网络之间进程的通信。

CreateNamePipe

HANDLE CreateNamedPipeA(
  LPCSTR                lpName,  // \.\pipe<i>pipename
  DWORD                 dwOpenMode,
  DWORD                 dwPipeMode,
  DWORD                 nMaxInstances,
  DWORD                 nOutBufferSize,
  DWORD                 nInBufferSize,
  DWORD                 nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

ConnectNamePipe

BOOL ConnectNamedPipe(
  HANDLE       hNamedPipe,
  LPOVERLAPPED lpOverlapped
);

示例:
服务端

void CChildView::OnCreateNamePipe()
{
    //1创建一个命名管道
    LPCTSTR szhPipeName = TEXT("\\\\.\\pipe\\mypipe");
    hNamedPipe = CreateNamedPipe(szhPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL);
    if (hNamedPipe == INVALID_HANDLE_VALUE)
    {
        TRACE("CreateNamedPipe failed with %d\n", GetLastError());
        MessageBox(_T("创建命名管道失败"));
        return;
    }
    //2等待客户端的连接
    HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    if (hEvent == NULL)
    { 
        MessageBox(_T("创建事件失败"));
        CloseHandle(hNamedPipe);
        hNamedPipe = NULL;
        return;
    }
    OVERLAPPED ovlap;
    ZeroMemory(&ovlap,sizeof(OVERLAPPED));
    ovlap.hEvent = hEvent;
    if (!ConnectNamedPipe(hNamedPipe, &ovlap))
    {
        if(GetLastError() != ERROR_IO_PENDING)
        {
            MessageBox(_T("等待客户端连接失败"));
            CloseHandle(hNamedPipe);
            CloseHandle(hEvent);
            hNamedPipe = NULL;
            hEvent = NULL;
            return;
        }
    }
    if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
    {
        MessageBox(_T("等待对象失败"));
        CloseHandle(hNamedPipe);
        CloseHandle(hEvent);
        hNamedPipe = NULL;
        hEvent = NULL;
        return;
    }
}


void CChildView::OnSreadNamePipe()
{
    //读取数据
    char szBuf[100] = { 0 };
    DWORD dwRead;
    if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
    {
        MessageBox(_T("读取数据失败"));
        return;
    }
    MessageBox((CStringW)szBuf);
}

void CChildView::OnSwriteNamePipe()
{
    //写入数据
    char szBuf[] = "ZYX is handsome Server";
    DWORD dwWrite;
    if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
    {
        MessageBox(_T("写入数据失败"));
        return;
    } 
}

客户端

void CChildView::OnConNamePipe()
{
    LPCTSTR szNamePipeName = TEXT("\\\\.\\pipe\\mypipe");

    if (WaitNamedPipe(szNamePipeName, NMPWAIT_WAIT_FOREVER) == 0)
    {
        MessageBox(_T("当前没有可以利用的管道"));
        return;
    }
    hNamedPipe = CreateFile(szNamePipeName,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (hNamedPipe == INVALID_HANDLE_VALUE)
    {
        MessageBox(_T("打开命名管道失败!"));
        hNamedPipe = NULL;
        return;
    }
    
}


void CChildView::OnReadNamePipe()
{
    //读取数据
    char szBuf[100] = { 0 };
    DWORD dwRead;
    if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
    {
        MessageBox(_T("读取数据失败"));
        return;
    }
    MessageBox((CStringW)szBuf);
}


void CChildView::OnWriteNamePipe()
{
    // TODO: 在此添加命令处理程序代码
        //写入数据
    char szBuf[] = "ZYX is handsome Client";
    DWORD dwWrite;
    if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
    {
        MessageBox(_T("写入数据失败"));
        CloseHandle(hNamedPipe);
        return;
    }
    CloseHandle(hNamedPipe);
}

WM_COPYDATA

利用WM_COPYDATA这个消息进行通信。

是最常用、最灵活的进程间通信方式。

一个应用程序发送WM_COPYDATA消息以将数据传递给另一个应用程序。

SPY++专门够用来查找窗口句柄。

要给进程发送数据,首先要拿到该窗口的句柄,也就是要拿到标题(因为句柄有可能会发生变化)。


发送端:

void CWMCOPYDATASENDDlg::OnBnClickedSend()
{
    //必须要知道接收端的标题 句柄 spy工具
    CString strWindowTitle = _T("MFCRecv");
    CString strDataToSend = _T("Hello");
    //获取句柄
    HWND hRecvWnd = ::FindWindow(NULL, strWindowTitle.GetBuffer(0));
    if (hRecvWnd != NULL && ::IsWindow(hRecvWnd))
    {
        //数据的封装
        COPYDATASTRUCT cpd;
        cpd.dwData = 0;
        cpd.cbData = strDataToSend.GetLength()*sizeof(TCHAR);
        cpd.lpData = (PVOID)strDataToSend.GetBuffer(0);
        ::SendMessage(hRecvWnd,WM_COPYDATA,(WPARAM)(AfxGetApp()->m_pMainWnd),(LPARAM)&cpd);
    }
    strDataToSend.ReleaseBuffer();
}

接收端:

BOOL CWMCOPYDATADlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    //消息响应函数
    //解析数据
    LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData);
    DWORD dwLength = pCopyDataStruct->cbData;
    TCHAR szRecvText[1024] = { 0 };
    memcpy(szRecvText,szText,dwLength);
    MessageBox(szRecvText, _T("Bingo"), MB_OK);
    return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

比较&总结

  • 剪贴板比较简单,剪贴板和匿名管道只能实现同一机器的两个进程通信。而不能实现网络进程之间的通信。
  • 邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收(单向)。
  • 命名管道和邮槽可以进程网络通信。命名管道只能是点对点的单一通信。
  • 邮槽的缺点就是传输的数据量很小,424字节以下。
  • WM_COPYDATA封装数据非常方便,如果数据量较大,建议使用命名管道。
相关文章
|
1天前
|
算法 调度 UED
深入理解操作系统的进程调度策略
【5月更文挑战第7天】 在现代计算机系统中,操作系统的核心职责之一是确保CPU资源的有效分配与利用。本文旨在探讨操作系统中的进程调度策略,并分析其对系统性能的影响。我们将从调度的基本概念出发,介绍几种常见的调度算法,如先来先服务、短作业优先和轮转调度等,并对它们的优缺点进行比较。此外,文章还将讨论多级反馈队列调度策略,它结合了多种调度方法的优点,以适应不同类型的工作负载。通过深入分析,本文旨在为读者提供一个清晰的框架,以理解操作系统如何管理并发执行的多个进程,以及这些管理策略如何影响系统的整体效率和响应性。
|
3天前
|
算法 调度
深入理解操作系统:进程管理与调度策略
【5月更文挑战第5天】 在现代计算机系统中,操作系统的核心职能之一是高效地管理计算机资源,尤其是处理多个并发运行的程序(进程)。本文将探讨操作系统中的进程管理机制,重点分析不同的进程调度策略及其对系统性能的影响。我们将从理论和实践的角度出发,比较各种调度算法的优劣,并提出在特定场景下如何选择最合适的调度策略。通过深入剖析进程调度的原理和实现细节,旨在为读者提供全面而深刻的认知框架,以便于更好地理解和优化操作系统的性能。
|
4天前
|
算法 调度 云计算
深入理解操作系统:进程管理与调度策略
【5月更文挑战第4天】本文将深入探讨操作系统中的关键组成部分——进程管理,以及如何通过有效的进程调度策略提升系统性能。我们将剖析进程的概念、状态转换和控制,并详细分析不同的进程调度算法,如先来先服务(FCFS)、短作业优先(SJF)和多级反馈队列(MLFQ)。文章旨在为读者提供一个清晰的框架,以理解操作系统如何处理并发任务,保证系统资源的有效利用和响应性。
|
6天前
|
负载均衡 算法 调度
深入理解操作系统:进程管理与调度策略
【5月更文挑战第2天】 在现代计算环境中,操作系统的核心职能之一是确保系统资源的高效利用和任务的顺畅执行。本文将探讨操作系统中的关键组件——进程管理及其调度策略。通过对进程的概念、生命周期以及调度算法的详细分析,我们旨在揭示操作系统如何协调多个运行中的程序,以实现快速响应和资源优化。文章还将讨论不同类型操作系统(如实时操作系统和通用操作系统)中进程调度策略的差异性及其对系统性能的影响。通过理论与实践相结合的方式,本文为读者提供了一个全面了解操作系统进程管理的平台。
|
7天前
|
负载均衡 算法 大数据
深入理解操作系统:进程管理和调度策略
【5月更文挑战第1天】 在现代操作系统的核心功能中,进程管理与调度策略是确保系统高效、稳定运行的关键。本文旨在深入剖析操作系统中的进程概念、进程状态转换以及进程调度机制。通过对先进先出、最短作业优先和时间片轮转等调度算法的比较分析,我们不仅揭示了它们在资源分配和任务执行中的应用,还讨论了它们在不同场景下的表现和局限性。此外,文章还将探讨多核处理器环境下的调度策略演变,以及未来操作系统在进程管理方面可能面临的挑战。
|
7天前
|
算法 调度
深入理解操作系统中的进程调度策略
【5月更文挑战第1天】在多任务操作系统中,进程调度策略是决定系统性能和响应能力的关键因素。本文将详细探讨现代操作系统中常见的进程调度算法——从简单的先来先服务(FCFS)到复杂的多级反馈队列(MLFQ),以及实时系统中的立即模式和时间片轮转(RR)。我们将分析每种调度策略的工作原理、优势、局限性以及它们如何影响操作系统的整体表现。通过比较不同策略在各种负载场景下的表现,读者将能更好地理解如何为特定应用选择最合适的调度策略。
|
7天前
|
Java 调度 开发者
构建高效微服务架构:后端开发的新趋势深入理解操作系统之进程调度策略
【4月更文挑战第30天】 随着企业数字化转型的不断深入,传统的单体应用逐渐不能满足快速迭代和灵活部署的需求。微服务架构以其高度模块化、独立部署和易于扩展的特性,成为现代后端开发的重要趋势。本文将探讨如何构建一个高效的微服务架构,包括关键的设计原则、技术选型以及可能面临的挑战。
|
8天前
|
安全 算法 网络安全
构筑网络长城:网络安全漏洞解析与防御策略深入理解操作系统:进程管理与调度策略
【4月更文挑战第30天】 在数字化时代,网络安全已成为维护信息完整性、确保数据流通安全和保障用户隐私的关键。本文将深入探讨网络安全的核心问题——安全漏洞,并分享关于加密技术的最新进展以及提升个人和企业安全意识的有效方法。通过对常见网络威胁的剖析,我们旨在提供一套综合性的网络防御策略,以助力读者构建更为坚固的信息安全防线。 【4月更文挑战第30天】 在现代操作系统的核心,进程管理是维持多任务环境稳定的关键。本文将深入探讨操作系统中的进程概念、进程状态转换及进程调度策略。通过分析不同的调度算法,我们将了解操作系统如何平衡各进程的执行,确保系统资源的高效利用和响应时间的最优化。文中不仅剖析了先来先
|
8天前
|
算法 调度
深入理解操作系统:进程管理与调度策略
【4月更文挑战第30天】 在现代计算机系统中,操作系统扮演着至关重要的角色。它不仅负责管理和协调计算机硬件资源,还为应用程序提供了一个稳定、高效的运行环境。本文将深入探讨操作系统中的进程管理机制和调度策略,以帮助读者更好地理解操作系统的工作原理和优化方法。
|
1天前
|
存储 安全 Linux
【探索Linux】P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )
【探索Linux】P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )
4 0