0%

Windows原理与应用(二)——程序进程与进程间通信

Windows原理与应用(二)——程序进程与进程间通信

系列

Windows原理与应用(一)—— 简介 | 德依安-BLOG (ddg-302.github.io)

[本文]Windows原理与应用(二)——程序进程与进程间通信 | 德依安-BLOG (ddg-302.github.io)

Windows原理与应用(三)——线程间通信与同步 | 德依安-BLOG (ddg-302.github.io)

程序、进程与线程

  • 进程与程序不同,它是程序执行时的标志,程序是存放在磁盘中的可执行文件,而进程是程序执行的一个过程,程序是静态的,进程是动态的

    一个程序可执行多次产生多个进程实例,进程随运行动态变化,进程共享计算机资源、互相通信,由OS管理

  • 线程是最小的执行单位,它几乎不拥有系统资源,它是系统调度的基本单位

    在单CPU单核技术中,OS不断切换线程运行,这个过程成为并发;多核或多CPU中的程序可以并行

    一个进程可以拥有多个线程,一个线程只属于一个进程

操作系统中的进程

  • 操作系统中的进程与用户进程并发运行,用户进程由操作系统创建和调用
  • 用户进程也可以创建和调用别的进程
  • 被创建的进程与创建者构成父子关系

进程与线程的关系图

进程与线程的关系图

进程对象结构

进程对象结构

  • Windows将硬件资源虚拟后分配给进程,还创建多种软件资源

    软件资源分为两类,一类是只能由操作系统管理和使用的内核资源,如权限令牌,底层时间,互斥量等;另一类是用户程序使用的资源,如普通内存空间,文件资源等

  • 内核资源:每个内核对象是由系统内和创建的一块内存区,这块内存区的数据结构成员对应对象的属性,在初次创建内核对象时,创建函数获得标识这个内核的句柄HANDLE),句柄只能由特定的API调用,不可以直接操作

  • 句柄:句柄是一个4B(64位机中为8B)长的数值,用于标识应用程序中的不同实例,要注意句柄不是指针,它只是表示一个对象,这个对象的物理地址由操作系统安排,物理地址并不是固定的,但句柄值是固定的,需要使用指定的API才可以操作句柄指向的对象

    句柄是进程相关的,同一个句柄值在不同进程中意义不同

    关闭使用完毕的资源句柄后,系统不会马上销毁它,内核对象生命期可以比创建它的进程长

进程创建过程

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

进程的创建与启动 - C#

  • 创建wpf工程

  • 实例化一个Process类并打开应用程序,Process类在System.Diagnostics命名空间下

1
2
3
4
5
6
7
8
9
10
11
using System.Diagnostics;

Process mycmd = new Process();
mycmd.StartInfo.FileName = "cmd.exe";// 设置启动程序
mycmd.StartInfo.CreateNoWindow = true;// 不开启窗口
mycmd.StartInfo.UseShellExecute = false; // 不使用shell
mycmd.StartInfo.RedirectStandardOutput = true; // 重定向输入输出
mycmd.StartInfo.RedirectStandardInput = true;

mycmd.Start(); // 启动进程
// 这个函数有返回值,true为启动成功,false为启动失败
  • 关闭应用程序
1
2
// 法1
mycmd.Kill(); // 直接杀死进程
1
2
3
4
5
6
// 法2
// 得到程序中所有正在运行的进程
Process[] preo = Process.GetProcesses();
foreach(var item in preo){
item.Kill(); // 杀死进程
}

进程间通信

  • 高级通信(IPC[1]
    • 传输数据量大,超过几十个字节
  • 低级通信(同步控制)
    • 传输数据量小,少于数个字节,或仅是位单位

进程间通信方法分类

  1. 共享内存(剪贴板、COM、DLL、DDE、文件映射)
  2. 消息WM_COPYDATA
  3. 邮槽
  4. 管道(有名管道,无名管道)
  5. Windows套接字
  6. NetBIOS特殊的网络应用

IPC选择考虑

  1. 通信主题是进程内的线程还是不同进程之间
  2. 进程是需要网络还是本地机制
  3. 通信的进程运行的OS是否有差异
  4. 有些进程通信机制是只用于图形化窗体界面的,而不适用于控制台程序
  5. 通信目的是用于同步控制还是大量数据的交换

管道通信

由操作系统创建管道对象,发送进程向管道中写数据,接收进程从管道中读数据

  • 无名管道

    • 只能在单一进程内使用
  • 有名管道

    • 以「文件名」来表示,可以用于不同进程间通信

管道通信实现

  1. 建立连接:服务端创建命名管道NamedPipeServerStream类实例,调用WaitForConnection方法等待连接,这个函数将阻塞线程
  2. 通信实现:建立连接后,客户端和服务端可以通过Stream流的Read方法和Write方法进行读写通信
  3. 连接终止:任何一方使用Close方法可以关闭管道连接

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
NamedPipeServerStream pipeserver; // 实例化

public void start_listen()
{
// 由于WaitForConnection()会阻塞线程,而这服务端客户端都写在一个进程内,所以要开启一个进程避免进程卡死
Task task = new Task(() =>
{
while (true)
{
pipeserver = new NamedPipeServerStream("my_pipe", PipeDirection.InOut, 2, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);// (管道名, 确定管道方向枚举值, 共享同一服务器实例的最大数量, 确定管道传输模式)
pipeserver.WaitForConnection();
receive_msg(); // 收到消息,调用处理
}

});
task.Start();
}

public void receive_msg()
{
// 新建Stream流准备读写
StreamReader sreader = new StreamReader(pipeserver);
StreamWriter swriter = new StreamWriter(pipeserver);
swriter.AutoFlush = true;

string msg = sreader.ReadLine(); // 读管道内的消息
if (msg == null)
return;
Console.WriteLine(msg);
swriter.WriteLine("信息接收完毕"); // 回复消息给发送方
swriter.Close(); // 关闭管道连接
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NamedPipeClientStream pclient = new NamedPipeClientStream(".", "my_pipe", PipeDirection.InOut); // 客户端连接名为「my_pipe」的管道

Task task = new Task(() =>
{
try
{
if (!pclient.IsConnected)
pclient.Connect(10000); // 设置超时等待
}
catch
{
MessageBox.Show("连接超时!", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 准备stream
StreamWriter swriter = new StreamWriter(pclient);
swriter.AutoFlush = true;
StreamReader sreader = new StreamReader(pclient);

swriter.WriteLine(msg); // 发送消息
string return_msg = sreader.ReadLine(); // 接收管道发回的消息
Console.WriteLine(return_msg);
});
task.Start();

重定向方式

  • 控制台进程文件描述符

    • 标准输入
    • 标准输出
    • 标准错误输出
  • 普通进程

    • 从键盘接收输入→输出到屏幕
  • 重定向

    • 从文件输入→屏幕输出
    • ……
  • 例子

    • 使用控制台重定向符><
1
2
3
dir > file.txt // 将dir指令的输出存放到file.txt中
cmd >> file.txt // 将接下来的所有屏幕输出都存放到file.txt,输入exit退出
cmd < file.txt // 将file.txt中的指令输出到cmd

使用控制台重定向获取控制台输出实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.Diagnostics;

Process mycmd = new Process();
mycmd.StartInfo.FileName = "cmd.exe";// 设置启动程序
mycmd.StartInfo.CreateNoWindow = true;// 不开启窗口
mycmd.StartInfo.UseShellExecute = false; // 不使用shell
mycmd.StartInfo.RedirectStandardOutput = true; // 重定向输入输出
mycmd.StartInfo.RedirectStandardInput = true;

mycmd.Start(); // 启动进程
// 这个函数有返回值,true为启动成功,false为启动失败

public void excute_command(string command)
{
// StandardInput重定向输入
mycmd.StandardInput.WriteLine(command);
Console.WriteLine("输入");

while (true)
{
// 按行读取
// StandardOutput重定向输出
Console.WriteLine(mycmd.StandardOutput.ReadLine());
if (mycmd.StandardOutput.EndOfStream)
break; // 读完退出
}
}

练习

  1. 通过重定向机制实现进程间通信
    • 调用getmac获取网卡mac
    • 调用shutdown命令关闭或重启电脑
  2. 通过管道机制实现进程间通信
    • 客户端向服务端发送数据
    • 服务端显示数据

程序将会以WPF窗体程序的形式呈现,为了让输出显示在窗体上,将不使用Console.WriteLine()方法,而是会有一些特殊的处理,这也将在之后整理放出


  1. 1.IPC:interproces communications,是通信进程处于同一台机器中的通信机制,IPC经常使用C/S模式