从这里开始,我们开始学习与操作系统相关的知识。
首先我们学习进程。进程可以理解为一个一个运行的程序,计算机当前运行的进程可以在任务管理器中查看。
我们还需了解内核对象。
1.内核对象
1.1 内核对象实际上就是一个 内存块 ,由操作系统来进行管理;
1.2 内核对象 使用计数 ,用来表示当前共有几个程序在使用,当计数为零时,内核对象被操作系统回收;
1.3 内核对象具有 安全属性 (SECURITY_ATTRIBUTES),通常位于第一位;为0时,默认的安全属性取决于安全令牌(security token);
1.4 内核对象有 句柄表 。
2.进程
2.1 运行起来的程序就是进程;
2.2 程序编译的过程:
1.预处理(Preprocessing):预处理主要包括 (1)将所有的#define删除,并且展开所有的宏定义;(2)处理所有的条件预编译指令,比如#if #ifdef ;(3)处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置;(4)删除注释;(5)添加行号和文件标识;(6)保留所有的#pragma编译器指令;
2.编译(Compilation):编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码;一般分为6步 扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成、目标代码优化;
3.汇编(Assembly):汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
4.链接(Linking):通过调用链接器ld来链接程序运行需要的一大堆目标文件,以及所依赖的其它库文件,最后生成可执行文件;链接的主要内容是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接;链接的主要过程包括:地址和空间分配(Address and Storage Allocation),符号决议(Symbol Resolution),重定位(Relocation)等;链接分为静态链接和动态链接。
2.3 进程的组成
1.控制块(PCB):PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息;
2.数据段
3.程序段
2.4 创建进程的过程
1.创建进程内核对象;
2.创建虚拟地址空间;
3.将可执行文件和必要的dll代码以及数据加载到进程的地址空间;
3.创建一个进程
3.1 创建一个基于对话框的MFC应用程序
1.给对话框上添加两个按钮: 创建进程 和 结束进程 ;
2.给 创建进程 按钮添加事件处理程序: CProcessDlg::OnBnClickedButton1 ;
3.我们使用 ::CreateProcess 来创建一个进程;
void CProcessDlg::OnBnClickedButton1()
{
// 创建一个进程
STARTUPINFO si = {sizeof(si)}; // 设置窗口的信息
PROCESS_INFORMATION pi = {0}; // 接受进程的信息
wchar_t szCmdLine[MAX_PATH] = L""; // 要执行的命令
if(FALSE == ::CreateProcess(
L"D:\\all_works\\C\\DoodleJump\\Doodle Jump\\Doodle Jump\\Debug\\Doodle Jump.exe",
szCmdLine, // CommandLine 命令行参数
0, // 进程的安全属性
0, // 线程的安全属性
FALSE, // 继承性
NORMAL_PRIORITY_CLASS, // 进程的优先级,NORMAL_PRIORITY_CLASS表示标准优先级
0, // 环境块
0, // 进程的功能工作目录,0默认当前目录
&si, // 设置窗口的信息
&pi // 进程的信息
))
{
MessageBox(L"进程创建失败");
}
}
4.函数 ::CreateProcess 的第一个参数是进程执行的可执行文件所在路径,这里我自己写了一个小程序用来检测,可以更换为其他 exe 文件,只要路径正确就好;比如说我还测试了打开 TIM : L"D:\General Software\TIM\Bin\Tim.exe" 将第一个参数更改为 Tim 的可执行文件的路径即可;
5.运行程序,点击 创建进程 按钮,会打开我们设置的程序。
3.2 接下来我们先创建一个Win32应用程序
1.添加一个源文件;
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
for(int i=0; i<argc; i++)
{
cout << "=======>" << argv[i] << endl;
}
system("pause");
return 0;
}
2.直接运行会在终端上显示我们生成的可执行文件的路径所在地址,比如说我的运行结果就是: =======>D:\all_works\C\colin\操作系统\1-26-进程基础\testCommandLine\ConsoleAppli
cation1\Debug\ConsoleApplication1.exe
请按任意键继续. . .
3.3 我们基于 3.1 在创建一个进程
1.将 3.1 中的函数注释掉并重新完成 CProcessDlg::OnBnClickedButton1 函数;
2.不同之处就是我们修改了创建的进程将要打开的可执行文件变成了我们在 3.2 中写的程序生成的 exe 文件所在的路径;
void CProcessDlg::OnBnClickedButton1()
{
// 创建一个进程
STARTUPINFO si = {sizeof(si)}; // 设置窗口的信息
//PROCESS_INFORMATION pi = {0}; // 接受进程的信息
wchar_t szCmdLine[MAX_PATH] = L"aldfkjaldfjalkdf lakdjflakdfjklajf"; // 要执行的命令
if(FALSE == ::CreateProcess(
L"D:\\all_works\\C\\colin\\CPP\\1-26\\testCommandLine\\ConsoleApplication1\\Debug\\ConsoleApplication1.exe",
szCmdLine, // CommandLine 命令行参数
0, // 进程的安全属性
0, // 线程的安全属性
FALSE, // 继承性
NORMAL_PRIORITY_CLASS, // 进程的优先级,NORMAL_PRIORITY_CLASS表示标准优先级
0, // 环境块
0, // 进程的功能工作目录,0默认当前目录
&si, // 设置窗口的信息
&pi // 进程的信息
))
{
MessageBox(L"进程创建失败");
}
}
3.这样执行的结果就是:
======>aldfkjaldfjalkdf
======>lakdjflakdfjklajf
请按任意键继续…
这个进程是为了让我们可以更好地理解 ::CreateProcess 函数中的第二个参数 CommandLine 命令行参数 的作用;
就比方说我们在Linux下完成了一个程序的编译,生成了可执行文件 a.out ,那么我们在执行 ./a.out 时若是以带参数的形式 ./a.out 123456 asdf ,那么我们将会把 123456 和 asdf 作为参数传入到程序中,这两个字符串可以被程序使用。
3.4 使用 notepad 打开一个 txt 文件
1.基本与上述操作相同,不同之处就是将第一个参数换成 notepad 的可执行程序的路径,将第二个参数换成要打开的 txt 文件所在的绝对路径;
void CProcessDlg::OnBnClickedButton1()
{
// 创建一个进程
STARTUPINFO si = {sizeof(si)}; // 设置窗口的信息
//PROCESS_INFORMATION pi = {0}; // 接受进程的信息
wchar_t szCmdLine[MAX_PATH] = L" D:\\all_works\\C\\colin\\CPP\\1-26\\aaa.txt"; // 要执行的命令
if(FALSE == ::CreateProcess(
L"D:\\LearningSoftware\\Sublime Text 3\\sublime_text.exe",
szCmdLine, // CommandLine 命令行参数
0, // 进程的安全属性
0, // 线程的安全属性
FALSE, // 继承性
NORMAL_PRIORITY_CLASS, // 进程的优先级,NORMAL_PRIORITY_CLASS表示标准优先级
0, // 环境块
0, // 进程的功能工作目录,0默认当前目录
&si, // 设置窗口的信息
&pi // 进程的信息
))
{
MessageBox(L"进程创建失败");
}
}
3.5 给 结束进程 按钮添加消息处理函数
1.在 结束进程 按钮中,我们将使用 ::TerminateProcess 函数,由于其中也涉及到 PROCESS_INFORMATION 类型的参数,因此我们将其定义为类成员;
void CProcessDlg::OnBnClickedButton2()
{
// TerminateProcess结束进程
::TerminateProcess(pi.hProcess, 0);
pi.hProcess = 0;
}
4.简易任务管理器
4.1 接下来我们要完成一个比较简单的任务管理器项目。
4.2 创建一个基于对话框的MFC应用程序
1.给主对话框添加一个 List Control 控件,四个按钮 Button 控件,分别命名为 结束进程 、 刷新 、 关机 、 修改内存 ;
2.我们首先给 List Control 控件添加一个变量 CListCtrl m_lsProcessInfo ;将 List Control 控件的 View 属性修改为 Report ;
3.再主对话框的初始化函数 CTaskManagerDlg::OnInitDialog 中插入四列,并更改风格;
BOOL CTaskManagerDlg::OnInitDialog()
{
... ...
//=======================对列表的操作================================
// 设置风格:点击某一行的第一列全选第一行
m_lsProcessInfo.SetExtendedStyle(LVS_EX_FULLROWSELECT);
// 给列表控件添加4列
m_lsProcessInfo.InsertColumn(0, L"进程名:", 0, 70);
m_lsProcessInfo.InsertColumn(1, L"进程ID:", 0, 70);
m_lsProcessInfo.InsertColumn(2, L"线程数:", 0, 70);
m_lsProcessInfo.InsertColumn(3, L"进程路径:", 0, 100);
// 测试数据
m_lsProcessInfo.InsertItem(0, L"AAAA");
m_lsProcessInfo.SetItemText(0, 1, L"BBB");
m_lsProcessInfo.SetItemText(0, 2, L"BBB");
m_lsProcessInfo.SetItemText(0, 3, L"BBB");
//=======================对列表的操作================================
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
4.3 给主对话框添加一个成员函数 void ShowAllProcessInfo() 用来显示当前计算机中的进程信息
1.首先创建一个用来存进程信息的结构体 PROCESSENTRY32 pe = {sizeof(pe)} ;
2.创建进程快照(就是将当前计算机中的进程信息进行一下采集): HANDLE h_snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) ;
3.若进程快照创建成功则获取进程信息,首先需要遍历快照中的所有进程,首先获取第一个进程: BOOL b_flag = ::Process32First(h_snapshot, &pe) ;
4.获取进程名并插入到第二行的第一列: m_lsProcessInfo.InsertItem(m_lsProcessInfo.GetItemCount(), pe.szExeFile) ;
5.获取进程ID并插入第二行的第二列: CString str_process_id ; str_process_id.Format(L"%d", pe.th32ProcessID) ; m_lsProcessInfo.SetItemText(m_lsProcessInfo.GetItemCount()-1, 1, str_process_id) ;
6.获取ID时的 Format 函数就是将结构体中的 th32ProcessID 的类型从 DWORD 改编为 CString ;
7.获取线程的数量: CString str_thread_count ; str_thread_count.Format(L"%d", pe.cntThreads) ; m_lsProcessInfo.SetItemText(m_lsProcessInfo.GetItemCount()-1, 2, str_thread_count) ;
8.查找下一个进程: b_flag = ::Process32Next(h_snapshot, &pe) ;
9.遍历完所有进程之后关闭句柄: ::CloseHandle(h_snapshot) ; h_snapshot = 0 ;
// 显示所有的进程信息
void CTaskManagerDlg::ShowAllProcessInfo()
{
// 创建存进程信息的结构体
PROCESSENTRY32 pe = {sizeof(pe)};
// 创建进程的快照
HANDLE h_snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(h_snapshot != INVALID_HANDLE_VALUE)
{
// 遍历快照中所有的进程
BOOL b_flag = ::Process32First(h_snapshot, &pe);
// 当有进程时,进行循环遍历
while (b_flag)
{
// =============将进程的信息放到列表中=====================
// 进程名
m_lsProcessInfo.InsertItem(m_lsProcessInfo.GetItemCount(), pe.szExeFile);
// 进程ID
CString str_process_id;
str_process_id.Format(L"%d", pe.th32ProcessID);
m_lsProcessInfo.SetItemText(m_lsProcessInfo.GetItemCount()-1, 1, str_process_id);
// 线程的数量
CString str_thread_count;
str_thread_count.Format(L"%d", pe.cntThreads);
m_lsProcessInfo.SetItemText(m_lsProcessInfo.GetItemCount()-1, 2, str_thread_count);
// 进程路径
// =============将进程的信息放到列表中=====================
b_flag = ::Process32Next(h_snapshot, &pe); // 查找下一个进程
}
}
// 关闭句柄
::CloseHandle(h_snapshot);
h_snapshot = 0;
}
4.4 在初始化对话框中进行调用,并且在 刷新 按钮的消息处理函数中进行调用
BOOL CTaskManagerDlg::OnInitDialog()
{
... ...
//=======================对列表的操作================================
// 设置风格:点击某一行的第一列全选第一行
m_lsProcessInfo.SetExtendedStyle(LVS_EX_FULLROWSELECT);
// 给列表控件添加4列
m_lsProcessInfo.InsertColumn(0, L"进程名:", 0, 70);
m_lsProcessInfo.InsertColumn(1, L"进程ID:", 0, 70);
m_lsProcessInfo.InsertColumn(2, L"线程数:", 0, 70);
m_lsProcessInfo.InsertColumn(3, L"进程路径:", 0, 100);
// 测试数据
//m_lsProcessInfo.InsertItem(0, L"AAAA");
//m_lsProcessInfo.SetItemText(0, 1, L"BBB");
//m_lsProcessInfo.SetItemText(0, 2, L"BBB");
//m_lsProcessInfo.SetItemText(0, 3, L"BBB");
// 调用显示进程信息的函数
this->ShowAllProcessInfo();
//=======================对列表的操作================================
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 刷新进程列表
void CTaskManagerDlg::OnBnClickedButton2()
{
// 首先删除所有的列表元素
m_lsProcessInfo.DeleteAllItems();
// 调用show函数
this->ShowAllProcessInfo();
}