Process Manager for Linux Phase 1

2021/4/7 7:06:35

本文主要是介绍Process Manager for Linux Phase 1,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Process Manager for Linux Phase 1

我的课设!

Requirement

这部分的设计要求是:

实现一个基于控制台的进程管理器,包含以下功能:

  1. 记录:将获取到的进程信息每一分钟保存到某个文档中,文档名称为当前的时间戳。
  2. 查找:用户输入要查找的进程ID,系统调用查找函数进行查找并显示结果。
  3. 显示:当用户输入指令时,或者每一分钟,显示当前的最新进程信息,包括进程的ID、进程的名
    称、进程的位数以及进程的CPU占有率。
  4. 排序:可按照进程ID大小、进程名字、CPU占有率进行排序。

Code Design

整体上,我们给整个 Project 设置了这样的结构:

image-20210406190322383

我们使用 Runtime 中的 class Loop 实现程序主题运行时刻的 Read-Evaluate-Print Loop 。

Item 中的一些类主要维护程序中涉及的各类数据。class Process 维护单个进程;class ProcessList 维护进程列表,同时实现更新、排序、查找等功能的接口;class Tokens 维护用户输入的指令信息。

此外,man_text 中用于集中存放一些手册、输出信息等内容,便于管理维护。iostaff 实现一些终端彩色输出,字符串标准化函数等输入输出杂项。

接下来我们分别看各部分的实现。

Utils

iostaff

// iostaff.h
#pragma once
#include <string>
#include <ostream>

class Form {
public:
    int type;
    Form(int x): type(x) {}
    friend std::ostream& operator << (std::ostream &os, const Form &itm);
};

const Form endo(0), tab(-1),
           bold(1), red(31),
           blue(34), green(32),
           highlit(43), italic(3);

int StringToInt(const std::string &s);
std::string NameStandlize(const std::string &s);
void PrintTableline();

// iostaff.cpp
#include <cctype>
#include <iostream>

#include "iostaff.h"

std::ostream& operator << (std::ostream& os, const Form& itm) {
    switch (itm.type) {
        case 0:
            os << "\033[39m\033[0m";
            break;
        case -1:
            os << "\t\t";
            break;
        default:
            os << "\033[" << itm.type << 'm';
    }
    return os;
}

int StringToInt(const std::string &s) {
    int ret = 0;
    for (auto digit: s) {
        if (!isdigit(digit)) {
            return 0;
        }
        ret = ret * 10 + digit - 48;
    }
    return ret;
}

std::string NameStandlize(const std::string &s) {
    std::string ret;
    for (auto cht: s) {
        if (isalpha(cht)) {
            ret.push_back(toupper(cht));
        }
    }
    return ret;
}

void PrintTableline() {
    std::cout << endo << blue;
    for (int i = 0; i != 80; ++i) {
        std::cout << "-";
    }
    std::cout << endo << std::endl;
}

基于这里 Form 类的实现,我们可以通过类似于:

int a, b;
std::cin >> a >> b;
std::cout << bold << italic << red << a << " " << b << endo << std::endl;

的方式,方便控制输出粗体、斜体、字体颜色内容,其中 endo 可清空当前流中的格式。

Text

man_text

// man_text.h
void TableHeading();
void def_manual();
void WaitInput();

// man_text.cpp
#include <iostream>
#include "../Utils/iostaff.h"

void def_manual() {
    using namespace std;
    PrintTableline();
    cout << bold << blue  << "Command" << tab << "Args" << tab << "Function" << endo << endl;
    PrintTableline();
    cout << bold << green << "help" << endo << tab 
                          << "None" << tab
                          << "Print the Command Manual"
                          << endl;

    cout << bold << green << "quit" << endo << tab 
                          << "None"  << tab
                          << "Quit the Task Manager for Linux"
                          << endl;

    cout << bold << green << "update" << endo << tab 
                          << "None"  << tab
                          << "Update the Process Infomation right now"
                          << endl;

    cout << bold << green << "find" << endo << tab
                 << green << "[Pid]" << endo << tab 
                          << "Find a process by its pid, or other dimensions"
                          << endl;

    cout << bold << green << "sort" << endo << tab
                 << green << "[COL] [ASC/DEC]" << endo
                 << " Sort info by given coloum name in given order"
                 << endl;

    cout << bold << green << "display" << endo << tab
                 << green << "[NUM]" << endo << tab
                 << "Motify the number of processes to display"
                 << endl;
    PrintTableline();
}

void TableHeading() {
    using namespace std;
    cout << bold << green << "PID"   << tab
                          << "%CPU"   << tab
                          << "%MEM"   << tab
                          << "COMMAND" << tab
                          << endo << endl;
}

void WaitInput() {
    std::cout << "TaskM>>" << bold << green << ">  " << endo;
}

这里只是基于 iostaff 输出了一些简单的文本信息。

Item

Tokens

// tokens.h
#pragma once
#include <istream>
#include <vector>
#include <string>

class Tokens {
private:
    std::vector<std::string> CmdLine;
    void AddElements(std::string s);

public:
    void Print();
    bool empty() const;
    std::string GetHead() const;
    std::string GetArgs(int ith) const;
    friend std::istream& operator >> (std::istream& is, Tokens& itm);
};

// tokens.cpp
#include <string>
#include <iomanip>
#include <cstdlib>
#include <iostream>

#include "tokens.h"
#include "../Utils/iostaff.h"

void Tokens::AddElements(std::string s) {
    CmdLine.push_back(s);
}

bool Tokens::empty() const {
    return CmdLine.empty();
}

std::string Tokens::GetHead() const {
    return NameStandlize(CmdLine[0]);
}

std::string Tokens::GetArgs(int ith) const {
    if (ith >= CmdLine.size()) {
        return std::string();
    }
    return CmdLine[ith];
}

std::istream& operator >> (std::istream& is, Tokens& ret) {
    std::string buf;
    std::getline(std::cin, buf);
    buf = buf + ' ';

    std::string::size_type ind = 0;
    while (true) {
        while (buf[ind] == ' ') ++ind;
        auto pos = buf.find(' ', ind);
        if (pos == std::string::npos) {
            break;
        }
        ret.AddElements(buf.substr(ind, pos - ind));
        ind = pos + 1;
    }
    return is;
}

void Tokens::Print() {
    for (auto itm: CmdLine) {
        std::cout << "[" << itm << "] ";
    }
    std::cout << std::endl;
}

只是一些简单的字符串处理操作。

Process

// process.h
#pragma once
#include <string>
#include <vector>
#include <iostream>

class Process {
private:
    int pid;
    int VirtualMem, ResidentSiz;
    double Ratio_CPU, Ratio_MEM;
    std::string RuningBits;
    std::string UserName, CommandStr, tty, RunStat, start, SysTime;

public:
    friend std::istream& operator >> (std::istream &ins, Process &itm);
    friend std::ostream& operator << (std::ostream &ous, const Process &itm);

    int GetPid() const;
    double GetRatioCPU() const;
    double GetRatioMEM() const;
    std::string GetCmd() const;
    // std::string& GetBit();
};

// process.cpp
#include <iomanip>
#include <istream>
#include <string>

#include "process.h"
#include "../Utils/iostaff.h"

int Process::GetPid() const {
    return pid;
}

double Process::GetRatioCPU() const {
    return Ratio_CPU;
}

double Process::GetRatioMEM() const {
    return Ratio_MEM;
}

std::string Process::GetCmd() const {
    return CommandStr;
}

// std::string& Process::GetBit() {
//     return RuningBits;
// }

std::istream& operator >> (std::istream &ins, Process &itm) {
    ins >> itm.UserName >> itm.pid >> itm.Ratio_CPU >> itm.Ratio_MEM;
    ins >> itm.VirtualMem >> itm.ResidentSiz >> itm.tty >> itm.RunStat;
    ins >> itm.start >> itm.SysTime;
    std::ostringstream oss;
    
    getline(ins, itm.CommandStr);
    return ins;
}

std::ostream& operator << (std::ostream &ous, const Process &itm){
    ous << itm.pid << tab
        << itm.Ratio_CPU << tab
        << itm.Ratio_MEM << tab
        << itm.CommandStr;
    return ous;
}

这里原定是要通过 file -L /proc/PID/exe | grep -o '..-bit' 获取进程的运行位数的。但该批量执行时,可能出现需要 root 权限、进程 ID 无效、调用时间超限等问题。且考虑到 Linux 下多运行位数进程的特性并不显著,因此放弃了该功能的实现。

process_list

// process_list.h
#pragma once
#include <vector>
#include <functional>

#include "process.h"

typedef std::vector<Process>::iterator pit;
class ProcessList {
private:
    std::vector<Process> lst;
    std::string DefaultCol;
    bool Defaultinc;
public:
    ProcessList();
    void UpdateList();
    void PrintList(bool FoundFlag, int Siz = 10);
    bool FindProcess(int TargetPid);
    void SortCol(std::string col, bool increasing);
};

// process_list.cpp
#include <ctime>
#include <string>
#include <iomanip>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>

#include "process.h"
#include "process_list.h"
#include "../Utils/iostaff.h"
#include "../Text/man_text.h"

ProcessList::ProcessList() {
    DefaultCol = "CPU";
    Defaultinc = false;
}

void ProcessList::UpdateList() {
    using namespace std;

    ostringstream oss;
    auto CurTime = time(nullptr);
    oss << put_time(gmtime(&CurTime), "%F-%T");

    string LogFile = "Logs/" + oss.str() + ".log", Heading;
    system(("ps auxc > " + LogFile).c_str());
    ifstream FileIn(LogFile);
    lst.clear();
    
    Process itm;
    getline(FileIn, Heading);
    while (FileIn >> itm) {
        lst.push_back(itm);
        oss.clear();
    }

    SortCol(DefaultCol, Defaultinc);
}

bool ProcessList::FindProcess(int TargetPid) {
    for (pit ite = lst.begin(); ite != lst.end(); ++ite) {
        if (ite->GetPid() == TargetPid) {
            std::rotate(lst.begin(), ite, ite + 1);
            return false;
        }
    }
    std::cerr << red <<
        "Cannot Find the given process" << endo << std::endl;
    return true;
}

void ProcessList::PrintList(bool FoundFlag, int Siz) {
    using namespace std;
    system("clear");
    PrintTableline();
    TableHeading();
    if (FoundFlag)  cout << bold << red;
    cout << lst[0] << endo << endl;
    for (int i = 1; i != Siz; ++i) {
        cout << lst[i] << endl;
    }
    PrintTableline();
}

// Done with the help from Barry on stackoverflow.com
void ProcessList::SortCol(std::string col, bool increasing) {
    DefaultCol = col;
    Defaultinc = increasing;

    auto sort_by = [&](auto projection) {
        if (increasing) {
            std::ranges::sort(lst, std::less(), projection);
        } else {
            std::ranges::sort(lst, std::greater(), projection);
        }
    };

    if (col == "PID")           sort_by(&Process::GetPid);
    else if (col == "CPU")      sort_by(&Process::GetRatioCPU);
    else if (col == "MEM")      sort_by(&Process::GetRatioMEM);
    else if (col == "COMMAND")  sort_by(&Process::GetCmd);
}

这里的 UpdateList 函数是基于 /bin/ps 获取进程信息。用 C++11 中的 std::put_time 结合字符串流操作,比较便利地处理了时间信息。

FindProcess 中使用了 std::rotate 将所查找的进程滚动至列表表头,便于后续显示输出操作。

SortCol 的实现使用了 C++20 新标准中的 ranges 库。最初实现时遇到了一些类型系统上的问题。在 Stack Overflow 上提问后,在 @Barry 的帮助下实现了该函数。具体参见 https://stackoverflow.com/questions/66901310

Runtime

loop

// loop.h
#pragma once
#include <mutex>
#include <ctime>
#include <thread>

#include "../Item/tokens.h"
#include "../Item/process_list.h"

class Loop {
private:
    time_t Systime;
    ProcessList prl;
    bool ElementFound;
    int DisplaySiz;
    static const int WaitingThread = 30;
    static const int WaitingUpdate = 60;

public:
    Loop();
    static int GetWaitUpdate();
    static int GetWaitThread();
    int& GetDisplaySiz();
    bool& GetElementFound();
    void EvalCmd(Tokens& cmd);
    void LogsCheck();
    bool UpdateTime();
    void REPLoop(); // Read-Evaluate-Print Loop
};

// loop.cpp
#include <ctime>
#include <memory>
#include <string>
#include <future>
#include <thread>
#include <iostream>

#include "loop.h"
#include "../Item/tokens.h"
#include "../Item/process.h"
#include "../Text/man_text.h"
#include "../Utils/iostaff.h"

Loop::Loop() {
    Systime = -1;
    DisplaySiz = 10;
}

bool Loop::UpdateTime() {
    auto new_time = std::time(nullptr);
    if (Systime == -1 || new_time - Systime >= GetWaitUpdate()) {
        Systime = new_time;
        prl.UpdateList();
        return true;
    }
    return false;
}

void Loop::LogsCheck() {
    system("mkdir -p Logs");
}

int Loop::GetWaitUpdate() {
    return WaitingUpdate;
}

int Loop::GetWaitThread() {
    return WaitingThread;
}

bool& Loop::GetElementFound() {
    return ElementFound;
}

int& Loop::GetDisplaySiz() {
    return DisplaySiz;
}

void Loop::EvalCmd(Tokens& cmd) {
    GetElementFound() = false;
    if (cmd.empty()) {
        std::cerr << red <<
            "Error Evaluating a empty Command" << std::endl;
        return;
    }

    if (cmd.GetHead() == "QUIT") {
        exit(0);
    }
    else if (cmd.GetHead() == "UPDATE") {
        auto new_time = std::time(nullptr);
        Systime = new_time;
        prl.UpdateList();
        prl.PrintList(GetElementFound(), GetDisplaySiz());
        WaitInput();
    }
    else if (cmd.GetHead() == "FIND") {
        int arg = StringToInt(cmd.GetArgs(1));
        if (arg == 0) {
            std::cerr << red <<
                "Error Evaluating the FindArgs" << endo << std::endl;
            WaitInput();
        } else {
            if (!prl.FindProcess(arg)) {
                GetElementFound() = true;
                prl.PrintList(GetElementFound(), GetDisplaySiz());
            }
            WaitInput();
        }
    }
    else if (cmd.GetHead() == "SORT") {
        auto col = NameStandlize(cmd.GetArgs(1)),
             ord = NameStandlize(cmd.GetArgs(2));

        bool OrdFlag;
        if (ord == "ASC")       OrdFlag = true;
        else if (ord == "DEC")  OrdFlag = false;
        else {
            std::cerr << red <<
                "Error sorting order input" << endo << std::endl;
            WaitInput();
        }

        prl.SortCol(col, OrdFlag);
        prl.PrintList(GetElementFound(), GetDisplaySiz());
        WaitInput();
    }
    else if (cmd.GetHead() == "DISPLAY") {
        int arg = StringToInt(cmd.GetArgs(1));
        if (arg == 0) {
            std::cerr << red <<
                "Command argument invaild" << endo << std::endl;
            WaitInput();
        } else {
            GetDisplaySiz() = arg;
            prl.PrintList(GetElementFound(), GetDisplaySiz());
            WaitInput();
        }
    } else {
        def_manual();
        WaitInput();
    }
    std::cout.flush();
}

void Loop::REPLoop() {
    Tokens UserCmd;
    GetElementFound() = false;

    auto update_thread = std::thread([&] {
        while (true) {
            if (UpdateTime()) {
                GetElementFound() = false;
                prl.PrintList(GetElementFound(), GetDisplaySiz());
                WaitInput();
                std::cout.flush();
            }
            std::this_thread::sleep_for(
                std::chrono::seconds(GetWaitThread()));
        }
    });

    Tokens TempCmd;
    while(true) {
        std::cin >> TempCmd;
        UserCmd = std::move(TempCmd);
        EvalCmd(UserCmd);
    }
    
    update_thread.join();
}

这里为了实现自动刷新输出的同时等待用户输入的功能,我们在 144 行引入了一个自动更新线程。该线程每经过 int WaitingThread 秒苏醒一次,检查距离上次更新信息是否超过了 int WaitingUpdate 时间。其中 WaitingThreadWaitingUpdate 的一半。这一设置是考虑到用户可能会手动进行更新操作,可能会造成相应的影响。

不过这里的实现存在一个问题:如果用户正在输入时进行了一次自动更新,那么目前的输入信息会丢失,可能会导致一些后续问题。因此刷新不宜过分频繁。

这里的 EvalCmd 基本在调用之前实现的接口,并做了一些朴素的异常处理。

Demo

image-20210406201119169


这篇关于Process Manager for Linux Phase 1的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程