Windows第二章-程序进程与进程间通信

一、进程

1.进程的定义

     进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位,具有动态性、并发性、独立性、异步性。

     Windows是一个多任务的系统,它能够同时运行多个程序,其中的每一个正在运行的程序就称为一个“进程”。Windows 2000及其以上版本,可以通过任务管理器查看系统当前运行的程序和进程。

     进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。

2.进程与程序的区别

  • 进程与程序不同,它是程序执行时的标志,不仅是程序的静态版本。
  • 用户创建一个进程后,操作系统就将程序的一个副本装入计算机中,然后启动一个线程执行该程序。
  • 进程是程序在OS中的一次运行时程序的复制品,一个程序可以执行多次产生多个进程实例。

3.并发与并行

     多核或者CPU机器中程序可以并行执行,单CPU单核技术的机器中OS多个线程代码的运行称为并发。
     
(1)并发

  • 并发是指系统或应用程序在某一时间段内同时处理多个事务的运行过程。对于单处理器的计算机系统来说,由于单个CPU在任何时刻只能执行一个线程,所以,这种计算机系统的并发,实际上是通过操作系统在各个正在执行的线程之间切换CPU,以分时处理的方式实现表面形式上的并发,只是因为其切换的速度快且处理能力强时,用户直观感觉不到而已。
  • 并发的实质是一个或多个物理CPU在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
  • 微观角度:所有的并发处理都有排队等候,唤醒,执行等这样的步骤,在微观上他们都是序列被处理的,如果是同一时刻到达的请求(或线程)也会根据优先级的不同,而先后进入队列排队等候执行。
  • 宏观角度:多个几乎同时到达的请求(或线程)在宏观上看就像是同时在被处理。

(2)并行

  • 并行指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源上(多核),同时执行。对于多处理器的计算机系统,其多个CPU之间既有相互协作,又有独立分工,所以,它们在各自执行一个相应线程时可以互不影响,同时进行,实现并行处理。
    在这里插入图片描述
    (OS读取程序文件,配置进程控制块形成内存进程,进程控制块包含进程需要的各项资源,主线程从程序入口开始执行,并使用特殊方法创建工作线程)

4.进程对象结构

从高层次的抽象来看,一个Windows进程是由以下元素构成的。

  1. 一个私有的虚拟地址空间。(虚拟地址空间指的是一组虚拟内存地址的范围)
  2. 一个可以执行的程序。它定义了初始的代码和数据,并且被映射到该进程的虚拟地址空间。即可执行映像。
  3. 一个已打开句柄的列表,这些句柄指向各种系统资源。该进程内的所有线程都可以访问。
  4. 一个被称为访问令牌的安全环境,它标识了与该进程关联的用户、安全组和特权。
  5. 一个被称为进程ID的唯一标识符。
  6. 至少一个执行线程。(第一个执行线程称为主线程)

除此之外,运行中的进程对象包含的资源内容有进程ID、父进程ID、退出码、内存信息、异常端口、调试端口、设置映像、映像文件名、映像基址、进程优先级、访问令牌、句柄表和活动进程链等。

在这里插入图片描述
                                   图:进程资源与句柄表示意图

        Windows操作系统将硬件资源虚拟以后分配给进程,还创建多种软件资源。软件资源分为两类,一类是用于操作系统的运行只能由操作系统管理和使用的叫内核资源,还有一类是用户程序使用的资源。具体的内核对象包括权限令牌、底层事件对象、文件、文件映像、IO完成端口、作业邮槽、互斥量、管道进程、信号量、进程和线程对象等。用户程序资源有普通内存空间、窗体显示资源和文件资源。

         每个内核对象是由系统内核创建的一块内存区,这块内存区的数据结构成员对应对象的属性。某些成员在不同内核对象中均存在,如访问权限与引用计数,而一些成员则是专属于特定内核对象,如进程对象具有进程ID值、基本优先级和退出代码;文件对象具有字节偏移量、共享模式与打开方式。内核对象只能由操作系统创建与操作,应用程序不允许直接修改内核对象内容,限制条件保证内核对象的修改不影响应用程序执行。

         初次创建内核对象时,创建函数获得标识这个内核对象的 HANDLE值,再通过特定函数通过 HANDLE值操作内核对象的内容。某些操作系统把指向私有对象的指针及进程中内部数组下标称为句柄,但在 Windows系统中是标识系统中对象的特殊值。句柄是Windows编程的基础,在 WinNT.h文件中句柄定义为:

 typedef PVOID HANDLE;

5.句柄

      句柄的定义:在windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为他们分配内存,并返回标示这些资源的标示号,即句柄。句柄指的是一个核心对象在某一个进程中的唯一索引,而不是指针。

      句柄是一个4B(64位程序中为8B)长的数值,用于标识应用程序中的不同实例,应用程序通过句柄访问相应对象的信息。因为 Windows的内存管理经常将当前空闲对象的内存释放掉,当需要时访问再重新提交到物理存储,所以对象的物理地址是变化的。OS禁止程序用物理地址访问对象内容,句柄值并不是对象的指针,虽然句柄的值是确定的,但是必须使用指定的AP才可操作句柄指向的对象,例如使用 Duplicate Handle用于复制一个句柄对象,而非简单的变量赋值的方法来获得。

      系统在进程初始化时分配一个当前进程所有内核对象的句柄表,理论上句柄个数有2^32,实际个数受可用内存大小的限制。

      句柄表中每项内容具有内核对象的指针、访问权限掩码及一些标志位,内核对象创建函数返回句柄值,它能被进程内的线程使用。HANDLE值是进程相关的,相同的 HANDLE值在不同的进程中意义是不一样的,不代表相同的内核对象。如果 HANDLE值设计为全局唯一,特定进程很容易地获得其他进程的内核对象,将会对其他进程造成严重威胁。安全描述符保护创建的内核对象权限,控制进程是否有权进行访问,进程访问内核对象要获得相应的安全权限。

      进程调用 CloseHandle函数关闭使用完毕的资源句柄,系统不马上销毁它,内核对象生命期可比创建它的进程长,内核对象能被其他进程引用。OS通过引用计数记录使用内核对象的进程/线程个数,内核对象最先创建时引用计数值为1,其他进程对其访问记数值递增, Close Handle函数递减内核引用记数,系统销毁引用数为0内核对象。

6.进程对象数据结构

      KPROCESS(PCB)是EPROCESS的一部分, EPROCESS存在于内核上层的执行体中, KROCESS(PCB)存在于内核下层的微内核中, 各施其职。
      每个进程都在内核中都有一个EPROCESS, 所有进程共享同一个内核空间, 内核通过一个链表把所有的进程内核块(EPROCESS)串连起来。
      EPROCESS包含了很多重要的进程信息: PCB, 进程ID, 进程token, 进程句柄表, 进程环境块(PEB), Win32进程块(W32PROCESS)。
在这里插入图片描述
具体参考:Windows笔记Windows进程数据结构及创建流程

 

二、进程的创建

1.创建进程过程

  1. 打开文件映像(.exe)。
  2. 创建windows进程对象。
  3. 创建初始线程对象,包括上下文,堆栈。
  4. 通知内核系统为进程运行作准备。
  5. 执行初始线程。
  6. 导入需要的DLL,初始化地址空间,由程序入口地址开始执行进程。

2.进程的创建

      C#的System.Diagnostics命名空间下的Process类专门用于完成系统的进程管理任务,通过实例化一个Process类,就可以启动一个独立进程。

Process cmdP = new Process();
cmdP.StartInfo.FileName = "cmd.exe";
cmdP.StartInfo.CreateNoWindow = true;
cmdP.StartInfo.UseShellExecute = false;
cmdP.StartInfo.RedirectStandardOutput = true;
cmdP.StartInfo.RedirectStandardInput = true;
cmdP.Start();

      ProcessStartInfo类,则可以为Process定制启动参数,比如RedirectStandardInput、RedirectStandardOutput、RedirectStandardError,分别重定向了进程的输入、输出、错误流。

3.其他操作

打开应用程序

 Process.Start("calc"); //计算器
 Process.Start("mspaint"); // 画图工具
 Process.Start("notepad");// 记事本
 Process.Start("iexplore","http://www.baidu.com");

关闭应用程序

// 得到程序中所有正在运行的进程
Process[] preo = Process.GetProcesses(); 
foreach (var item in preo){
                Console.WriteLine(item);
                item.Kill(); //杀死进程 
}
         

 

三、进程间通信机制简介

         windows 操作系统提供进程间数据共享和通信的机制,称为 interprocess communications (IPC),IPC经常使用C/S模式。

1.进程间通信方法分类:

  1. 共享内存(剪贴板、COM、DLL、DDE、文件映射)
  2. 消息WM_COPYDATA(使用SendMessage向另一进程发送WM_COPYDATA消息)
  3. 邮槽 (windows操作系统提供的一种单向进程间通信机制)
  4. 管道,分有名管道与无名管道、进程重定向
  5. Windows套接字
  6. NetBIOS特殊的网络应用

2.消息WM_COPYDATA
         WM_COPYDATA消息的主要目的是允许在进程间传递只读数据。Windows在通过WM_COPYDATA消息传递期间,不提供继承同步方式。SDK文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就不可能删除和修改数据。
这个函数的原型及其要用到的结构如下:

SendMessage(IntPtr wnd,int msg,IntPtr wP, ref COPYDATASTRUCT lParam);

其中,WM_COPYDATA对应的十六进制数为0x004A,wnd:接收消息的窗口的句柄。msg:指定被发送的消息类型。wP通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。 lParam通常是一个指向内存中数据的指针。在此种定义方式下,lParam指向一个COPYDATASTRUCT的结构:

#region 定义结构体
 public struct COPYDATASTRUCT
 {
        public IntPtr dwData;
        public int cbData;
           [MarshalAs(UnmanagedType.LPStr)]
            public string lpData;
 }
 #endregion

使用实例:

[DllImport("User32.dll", EntryPoint = "SendMessage")]private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP, ref COPYDATASTRUCT lParam);

[DllImport("User32.dll", EntryPoint = “PostMessage")]private static extern int PostMessage(IntPtr wnd,int msg,IntPtr wP, ref COPYDATASTRUCT lParam);

首先,在发送方,用FindWindow找到接受方的句柄,然后向接受方发送WM_COPYDATA消息.
接受方在DefWndProc事件中,来处理这条消息.DefWndProc如下:

protected override void DefWndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case WM_COPYDATA:
                    COPYDATASTRUCT mystr = new COPYDATASTRUCT();
                    Type mytype = mystr.GetType();
                    mystr = (COPYDATASTRUCT)m.GetLParam(mytype);
                    this.textBox1.Text = mystr.lpData;
                    break;
                default:
                    base.DefWndProc(ref m);
                    break;
            }
        }

参考资料:
c# 进程间同步实现 进程之间通讯的几种方法C#进程间通信

 

四、进程间通信-重定向机制

1.重定向的定义与意义

    输入重定向是指把命令(或可执行程序)的标准输入重定向到指定的文件中。也就是说,输入可以不来自键盘,而来自一个指定的文件。
    输出重定向是指把命令(或可执行程序)的标准输出或标准错误输出重新定向到指定文件中。这样,该命令的输出就不显示在屏幕上,而是写入到指定文件中。

普通进程从键盘接收输入,输出到屏幕:
在这里插入图片描述
使用文件作为进程的输入称为输入重定向:
在这里插入图片描述

    重定向的意义:当我们写完程序,想要在另一个平台上跑我们所写的程序的时候,就需要用到重定向输入输出,例如调用控制台进程实现以下功能:Ping远程主机、获取MAC地址getmac、关机shutdown、服务管理

2.两种重定向方式:异步与同步

    重定向有两中方式,即同步和异步。

同步调用进程:

 #region 同步调用进程
        private void button1_Click(object sender, EventArgs e)
        {
            Process process = new Process();
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.UseShellExecute = false;// 是否使用外壳程序  
            process.StartInfo.CreateNoWindow = true;// 是否在新窗口中启动该进程的值 
            process.StartInfo.RedirectStandardInput = true;// 重定向输入流  
            process.StartInfo.RedirectStandardOutput = true;// 重定向输出流
            string strCmd = "getmac";//调用getmac获取网卡mac
            process.Start();
            process.StandardInput.WriteLine(strCmd);
            process.StandardInput.WriteLine("exit");
            //获取输出信息
            richTextBox2.Text = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            process.Close();
        }
#endregion 同步调用进程

异步调用:异步调用中还包含了回调函数编写与设置和窗体消息处理函数重载

 #region 异步调用进程

        #region 定义常量消息值
        public static StringBuilder cmdOutput = null;
        public const int WM_COPYDATA = 0x004B;
        #endregion 定义常量消息值

        //动态链接库引入
        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        private static extern int SendMessage(IntPtr wnd, int msg, IntPtr wP, ref COPYDATASTRUCT lParam);
        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);

        private void button2_Click(object sender, EventArgs e)
        {
            cmdOutput = new StringBuilder();
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardInput = true;
            p.StartInfo.RedirectStandardOutput = true;

            p.OutputDataReceived += new DataReceivedEventHandler(strOutputHandler);
            p.Start();
            string strCmd = "shutdown -r -t 1200"; //shutdown - r - t 时间 设置重新启动倒计时  
            MessageBox.Show("电脑将在1200s之后关机");
            richTextBox2.Text = richTextBox2.Text + "电脑将在1200s之后关机" + "\r\n";
            p.StandardInput.WriteLine(strCmd);
            p.StandardInput.WriteLine("exit");
            p.BeginOutputReadLine();
        }
        
        private void strOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
        {
            //通过FindWindow API的方式找到目标进程句柄,然后发送消息
            IntPtr WINDOW_HANDLER = FindWindow(null, "windows实验一");
            if (WINDOW_HANDLER != IntPtr.Zero)
            {
                COPYDATASTRUCT mystr = new COPYDATASTRUCT();
                mystr.dwData = (IntPtr)0;
                if (isEmpty(outLine.Data))
                {
                    mystr.cbData = 0;
                    mystr.lpData = "";
                }
                else
                {
                    byte[] sarr = System.Text.Encoding.Unicode.GetBytes(outLine.Data);
                    mystr.cbData = sarr.Length + 1;
                    mystr.lpData = outLine.Data;
                }
                //发送消息
                SendMessage(WINDOW_HANDLER, WM_COPYDATA, (IntPtr)0, ref mystr);
            }
        }

        #region 接收消息
        protected override void DefWndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case WM_COPYDATA:
                    COPYDATASTRUCT mystr = new COPYDATASTRUCT();
                    Type mytype = mystr.GetType();
                    mystr = (COPYDATASTRUCT)m.GetLParam(mytype);
                    this.richTextBox2.Text += mystr.lpData + "\r\n";
                    break;
                default:
                    base.DefWndProc(ref m);
                    break;
            }
        }
        #endregion 接收消息
        #endregion 异步调用进程

 

五、进程间通信-管道机制

1.定义:
    管道机制是计算机提供的一种进程间通信 (IPC) 方式,由操作系统创建管道对象,发送进程可以向管道写入数据,接收进程由管道中读出数据。管道还可进行跨计算机的通信,可使用网络,也可使用文件等,它屏蔽低层实现机制提供给进程通信机制。有两种形式管道,有名管道与无名管道。

2.重要类:

  • AnonymousPipeClientStream 类:公开匿名管道流的客户端,该管道流既支持同步读写操作,也支持异步读写操作。
  • AnonymousPipeServerStream 类:公开匿名管道流的服务端,该管道既支持同步读写操作,也支持异步读写操作。
  • NamedPipeClientStream 类:公开命名管道流的客户端,该管道既支持同步读写操作,也支持异步读写操作。
  • NamedPipeServerStream 类:公开命名管道流的服务端,该管道既支持同步读写操作,也支持异步读写操作。

3.实现管道通信
 
命名管道 Server和 Client间通信的实现流程如下:

(1)建立连接:服务端创建一个命名管道服务端实例, Named PipeServerStream类的 WaitForConnection方法用以侦听来自客户端连接请求,该函数将阻塞线程。当客户端调用 Connect方法连接后,双方即可进行通信。

(2)通信实现:建立连接之后,客户端与服务器端即可通过 Stream流的Read方法和 Write方法进行读写通信

(3)连接终止:当客户端与服务端的通信结束,或由于某种原因一方需要断开时,可使用 Close方法关闭管道连接。

在这里插入图片描述

4.使用命名管道进行进程通信的实例
客户端代码:

using System;
using System.IO;
using System.IO.Pipes;

namespace pipes
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NamedPipeClientStream pipeClient =
            new NamedPipeClientStream(".", "testpipe", PipeDirection.In))
            {

                // Connect to the pipe or wait until the pipe is available.
                Console.WriteLine("客户端");
                Console.WriteLine("尝试连接到管道...");
                pipeClient.Connect();

                Console.WriteLine("管道连接成功");
                Console.WriteLine("现在有{0}个管道服务实例打开.",
                   pipeClient.NumberOfServerInstances);
                using (StreamReader sr = new StreamReader(pipeClient))
                {
                    // Display the read text to the console
                    string temp;
                    while ((temp = sr.ReadLine()) != null)
                    {
                        Console.WriteLine("从服务端接受到的:{0}", temp);
                    }
                }
            }
            Console.Write("按Enter键以继续...");
            Console.ReadLine();
        }
    }
}

服务端代码:

using System;
using System.IO;
using System.IO.Pipes;

namespace pipes2
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.Out))
            {
                Console.WriteLine("命名管道服务端对象创建成功");
                Console.Write("服务端");
                // Wait for a client to connect
                Console.WriteLine("等待客户端开启...");
                pipeServer.WaitForConnection();

                Console.WriteLine("客户端连接成功");
                try
                {
                    // Read user input and send that to the client process.
                    using (StreamWriter sw = new StreamWriter(pipeServer))
                    {
                        sw.AutoFlush = true;
                        Console.Write("输入文本: ");
                        sw.WriteLine(Console.ReadLine());
                        Console.Read();
                    }
                }
                // Catch the IOException that is raised if the pipe is broken
                // or disconnected.
                catch (IOException e)
                {
                    Console.WriteLine("ERROR: {0}", e.Message);
                }
            }
        }
    }
}

实验结果:
在这里插入图片描述

本节参考资料:
C#中使用命名管道进行进程通信的实例添加链接描述
官方文档System.IO.Pipes 命名空间
官方文档NamedPipeServerStream 类

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值