Browse Source

add process

master
zhaohe 3 years ago
parent
commit
2d11434c5c
  1. 9
      core/components/process/.clang-format
  2. 8
      core/components/process/README.md
  3. 40
      core/components/process/process.cpp
  4. 133
      core/components/process/process.hpp
  5. 614
      core/components/process/process_unix.cpp
  6. 5
      module.cmake

9
core/components/process/.clang-format

@ -0,0 +1,9 @@
IndentWidth: 2
AccessModifierOffset: -2
UseTab: Never
ColumnLimit: 0
MaxEmptyLinesToKeep: 2
SpaceBeforeParens: Never
BreakBeforeBraces: Custom
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
NamespaceIndentation: None

8
core/components/process/README.md

@ -0,0 +1,8 @@
# tiny-process-library
使用说明:
```
```

40
core/components/process/process.cpp

@ -0,0 +1,40 @@
#include "process.hpp"
namespace iflytop {
namespace process {
Process::Process(const string_type &command, const string_type &path,
std::function<void(const char *bytes, size_t n)> read_stdout,
std::function<void(const char *bytes, size_t n)> read_stderr,
bool open_stdin, const Config &config) noexcept
: closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) {
open(command, path);
async_read();
}
Process::Process(const string_type &command, const string_type &path,
const environment_type &environment,
std::function<void(const char *bytes, size_t n)> read_stdout,
std::function<void(const char *bytes, size_t n)> read_stderr,
bool open_stdin, const Config &config) noexcept
: closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) {
open(command, path, &environment);
async_read();
}
Process::~Process() noexcept {
close_fds();
}
Process::id_type Process::get_id() const noexcept {
return data.id;
}
bool Process::write(const std::string &str) {
return write(str.c_str(), str.size());
}
} // namespace process
} // namespace iflytop

133
core/components/process/process.hpp

@ -0,0 +1,133 @@
#ifndef TINY_PROCESS_LIBRARY_HPP_
#define TINY_PROCESS_LIBRARY_HPP_
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <sys/wait.h>
#include <thread>
#include <unordered_map>
#include <vector>
namespace iflytop {
namespace process {
/// Additional parameters to Process constructors.
struct Config {
/// Buffer size for reading stdout and stderr. Default is 131072 (128 kB).
std::size_t buffer_size = 131072;
/// Set to true to inherit file descriptors from parent process. Default is false.
/// On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false.
bool inherit_file_descriptors = false;
/// On Windows only: controls how the process is started, mimics STARTUPINFO's wShowWindow.
/// See: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-startupinfoa
/// and https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-showwindow
enum class ShowWindow {
hide = 0,
show_normal = 1,
show_minimized = 2,
maximize = 3,
show_maximized = 3,
show_no_activate = 4,
show = 5,
minimize = 6,
show_min_no_active = 7,
show_na = 8,
restore = 9,
show_default = 10,
force_minimize = 11
};
/// On Windows only: controls how the window is shown.
ShowWindow show_window{ShowWindow::show_default};
};
/// Platform independent class for creating processes.
/// Note on Windows: it seems not possible to specify which pipes to redirect.
/// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false,
/// the stdout, stderr and stdin are sent to the parent process instead.
class Process {
public:
typedef pid_t id_type;
typedef int fd_type;
typedef std::string string_type;
typedef std::unordered_map<string_type, string_type> environment_type;
private:
class Data {
public:
Data() noexcept;
id_type id;
int exit_status{-1};
};
public:
/// Starts a process with the environment of the calling process.
Process(const string_type &command, const string_type &path = string_type(),
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
bool open_stdin = true, /*默认打开标准输入,使得其行为更接近于system()*/
const Config &config = {}) noexcept;
/// Starts a process with specified environment.
Process(const string_type &command,
const string_type &path,
const environment_type &environment,
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
bool open_stdin = true,
const Config &config = {}) noexcept; /// Starts a process with specified environment.
/// Starts a process with the environment of the calling process.
/// Supported on Unix-like systems only.
Process(const std::function<void()> &function,
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
bool open_stdin = true,
const Config &config = {}) noexcept;
~Process() noexcept;
/// Get the process id of the started process.
id_type get_id() const noexcept;
/// Wait until process is finished, and return exit status.
int get_exit_status() noexcept;
/// If process is finished, returns true and sets the exit status. Returns false otherwise.
bool try_get_exit_status(int &exit_status) noexcept;
/// Write to stdin.
bool write(const char *bytes, size_t n);
/// Write to stdin. Convenience function using write(const char *, size_t).
bool write(const std::string &str);
/// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent.
void close_stdin() noexcept;
/// Kill the process. force=true is only supported on Unix-like systems.
void kill(bool force = false) noexcept;
/// Kill a given process id. Use kill(bool force) instead if possible. force=true is only supported on Unix-like systems.
static void kill(id_type id, bool force = false) noexcept;
void signal(int signum) noexcept;
// void show_all_child_process();
private:
Data data;
bool closed;
std::mutex close_mutex;
std::function<void(const char *bytes, size_t n)> read_stdout;
std::function<void(const char *bytes, size_t n)> read_stderr;
std::thread stdout_stderr_thread;
bool open_stdin;
std::mutex stdin_mutex;
Config config;
std::unique_ptr<fd_type> stdout_fd, stderr_fd, stdin_fd;
id_type open(const string_type &command, const string_type &path, const environment_type *environment = nullptr) noexcept;
id_type open(const std::function<void()> &function) noexcept;
void async_read() noexcept;
void close_fds() noexcept;
};
// 为了确保所有子进程被安全回收,在主进程退出时,调用下面的方法,确保子进程的退出。
void killallsubprocess(int, void *);
} // namespace process
} // namespace iflytop
#endif // TINY_PROCESS_LIBRARY_HPP_

614
core/components/process/process_unix.cpp

@ -0,0 +1,614 @@
#include "process.hpp"
#include <algorithm>
#include <bitset>
#include <cstdlib>
#include <fcntl.h>
#include <poll.h>
#include <set>
#include <signal.h>
#include <stdexcept>
#include <string.h>
#include <unistd.h>
//
#include <dirent.h>
#include <linux/limits.h>
#include <list>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/times.h>
#include <time.h>
#include <unistd.h>
namespace iflytop {
namespace process {
using namespace std;
typedef long long int num;
typedef struct pid_stat_fields {
num pid;
char tcomm[PATH_MAX];
char state;
num ppid;
num pgid;
num sid;
num tty_nr;
num tty_pgrp;
num flags;
num min_flt;
num cmin_flt;
num maj_flt;
num cmaj_flt;
num utime;
num stime;
num cutime;
num cstime;
num priority;
num nicev;
num num_threads;
num it_real_value;
unsigned long long start_time;
num vsize;
num rss;
num rsslim;
num start_code;
num end_code;
num start_stack;
num esp;
num eip;
num pending;
num blocked;
num sigign;
num sigcatch;
num wchan;
num zero1;
num zero2;
num exit_signal;
num cpu;
num rt_priority;
num policy;
} pid_stat_fields;
void readone(num *x, FILE *input) { fscanf(input, "%lld ", x); }
void readunsigned(unsigned long long *x, FILE *input) { fscanf(input, "%llu ", x); }
void readstr(char *x, FILE *input) { fscanf(input, "%s ", x); }
void readchar(char *x, FILE *input) { fscanf(input, "%c ", x); }
int filter(const struct dirent *dir) { // select number folder
int i;
int n = strlen(dir->d_name);
for(i = 0; i < n; i++) {
if(!isdigit(dir->d_name[i])) // 返回所有数字目录
return 0;
else
return 1;
}
return 0;
}
pid_stat_fields populate_pid_stats(FILE *stat_file) {
pid_stat_fields stats;
// reading order is important!!!
readone(&(stats.pid), stat_file);
readstr(&(stats.tcomm[0]), stat_file);
readchar(&(stats.state), stat_file);
readone(&(stats.ppid), stat_file);
readone(&(stats.pgid), stat_file);
readone(&(stats.sid), stat_file);
readone(&(stats.tty_nr), stat_file);
readone(&(stats.tty_pgrp), stat_file);
readone(&(stats.flags), stat_file);
readone(&(stats.min_flt), stat_file);
readone(&(stats.cmin_flt), stat_file);
readone(&(stats.maj_flt), stat_file);
readone(&(stats.cmaj_flt), stat_file);
readone(&(stats.utime), stat_file);
readone(&(stats.stime), stat_file);
readone(&(stats.cutime), stat_file);
readone(&(stats.cstime), stat_file);
readone(&(stats.priority), stat_file);
readone(&(stats.nicev), stat_file);
readone(&(stats.num_threads), stat_file);
readone(&(stats.it_real_value), stat_file);
readunsigned(&(stats.start_time), stat_file);
readone(&(stats.vsize), stat_file);
readone(&(stats.rss), stat_file);
readone(&(stats.rsslim), stat_file);
readone(&(stats.start_code), stat_file);
readone(&(stats.end_code), stat_file);
readone(&(stats.start_stack), stat_file);
readone(&(stats.esp), stat_file);
readone(&(stats.eip), stat_file);
readone(&(stats.pending), stat_file);
readone(&(stats.blocked), stat_file);
readone(&(stats.sigign), stat_file);
readone(&(stats.sigcatch), stat_file);
readone(&(stats.wchan), stat_file);
readone(&(stats.zero1), stat_file);
readone(&(stats.zero2), stat_file);
readone(&(stats.exit_signal), stat_file);
readone(&(stats.cpu), stat_file);
readone(&(stats.rt_priority), stat_file);
readone(&(stats.policy), stat_file);
return stats;
}
pid_stat_fields get_pid_stats(num pid) {
char stat_file_name[PATH_MAX];
sprintf(stat_file_name, "/proc/%lld/stat", pid);
FILE *stat_file = fopen(stat_file_name, "r");
if(stat_file == NULL) {
throw runtime_error("Could not open /proc/pid/stat");
}
pid_stat_fields stat = populate_pid_stats(stat_file);
fclose(stat_file);
return stat;
}
list<pid_stat_fields> get_pid_stats_list() {
struct dirent **namelist;
int total = scandir("/proc", &namelist, filter, alphasort);
list<pid_stat_fields> pid_stats_list;
for(int i = 0; i < total; i++) {
pid_stat_fields stat = get_pid_stats(atoi(namelist[i]->d_name));
pid_stats_list.push_back(stat);
free(namelist[i]);
}
free(namelist);
return pid_stats_list;
}
/**
* @brief ,
*/
void get_child_processes(list<pid_stat_fields> allpids, list<pid_stat_fields> &childprocess, num pid) {
list<pid_stat_fields>::iterator it;
for(it = allpids.begin(); it != allpids.end(); it++) {
if((*it).ppid == pid && (*it).pid != pid) {
childprocess.push_back(*it);
get_child_processes(allpids, childprocess, (*it).pid);
}
}
}
/**
* @brief Get the child processes object
*
* @param pid
* @return list<pid_stat_fields>
*
*
*
*
*/
list<pid_stat_fields> get_child_processes(num pid) {
list<pid_stat_fields> allpids = get_pid_stats_list();
list<pid_stat_fields> childprocess;
get_child_processes(allpids, childprocess, pid);
return childprocess;
}
bool isprocessrunning(num pid) {
char stat_file_name[PATH_MAX];
sprintf(stat_file_name, "/proc/%lld/stat", pid);
FILE *stat_file = fopen(stat_file_name, "r");
if(stat_file == NULL) {
return false;
}
num _pid;
char tcomm[PATH_MAX];
char state;
readone(&(_pid), stat_file);
readstr(&(tcomm[0]), stat_file);
readchar(&(state), stat_file);
fclose(stat_file);
return state != 'Z';
}
void show_all_child_process(num pid) {
list<pid_stat_fields> childprocess_list = get_child_processes(pid);
list<pid_stat_fields>::iterator it;
for(it = childprocess_list.begin(); it != childprocess_list.end(); it++) {
printf("%lld %s\n", (*it).pid, (*it).tcomm);
}
}
void force_kill(num pid) {
bool firstkill = false;
int maxkilltimes = 3;
int killtimes = 0;
while(isprocessrunning(pid)) {
if(killtimes >= maxkilltimes) {
fprintf(stderr, "kill process %lld, fail %s\n", pid, strerror(errno));
break;
}
if(firstkill) {
fprintf(stderr, "rekill process %lld\n", pid);
}
::kill(pid, SIGTERM);
usleep(500 * 1000);
firstkill = true;
killtimes++;
}
};
void killallsubprocess(int, void *) {
list<pid_stat_fields> childprocess_list = get_child_processes(getpid());
// trykill all child process
for(auto &childprocess : childprocess_list) {
if(isprocessrunning(childprocess.pid)) {
::kill(childprocess.pid, SIGTERM);
}
}
// forse kill al child process
for(auto &childprocess : childprocess_list) {
force_kill(childprocess.pid);
usleep(10 * 1000);
}
}
Process::Data::Data() noexcept : id(-1) {}
Process::Process(const std::function<void()> &function,
std::function<void(const char *, size_t)> read_stdout,
std::function<void(const char *, size_t)> read_stderr,
bool open_stdin, const Config &config) noexcept
: closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) {
open(function);
async_read();
}
Process::id_type Process::open(const std::function<void()> &function) noexcept {
if(open_stdin)
stdin_fd = std::unique_ptr<fd_type>(new fd_type);
if(read_stdout)
stdout_fd = std::unique_ptr<fd_type>(new fd_type);
if(read_stderr)
stderr_fd = std::unique_ptr<fd_type>(new fd_type);
int stdin_p[2], stdout_p[2], stderr_p[2];
if(stdin_fd && pipe(stdin_p) != 0)
return -1;
if(stdout_fd && pipe(stdout_p) != 0) {
if(stdin_fd) {
close(stdin_p[0]);
close(stdin_p[1]);
}
return -1;
}
if(stderr_fd && pipe(stderr_p) != 0) {
if(stdin_fd) {
close(stdin_p[0]);
close(stdin_p[1]);
}
if(stdout_fd) {
close(stdout_p[0]);
close(stdout_p[1]);
}
return -1;
}
id_type pid = fork();
if(pid < 0) {
if(stdin_fd) {
close(stdin_p[0]);
close(stdin_p[1]);
}
if(stdout_fd) {
close(stdout_p[0]);
close(stdout_p[1]);
}
if(stderr_fd) {
close(stderr_p[0]);
close(stderr_p[1]);
}
return pid;
}
else if(pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGTERM);
if(stdin_fd)
dup2(stdin_p[0], 0);
if(stdout_fd)
dup2(stdout_p[1], 1);
if(stderr_fd)
dup2(stderr_p[1], 2);
if(stdin_fd) {
close(stdin_p[0]);
close(stdin_p[1]);
}
if(stdout_fd) {
close(stdout_p[0]);
close(stdout_p[1]);
}
if(stderr_fd) {
close(stderr_p[0]);
close(stderr_p[1]);
}
if(!config.inherit_file_descriptors) {
// Optimization on some systems: using 8 * 1024 (Debian's default _SC_OPEN_MAX) as fd_max limit
int fd_max = std::min(8192, static_cast<int>(sysconf(_SC_OPEN_MAX))); // Truncation is safe
if(fd_max < 0)
fd_max = 8192;
for(int fd = 3; fd < fd_max; fd++)
close(fd);
}
setpgid(0, 0);
// TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe
// TODO: One solution is: echo "command;exit"|script -q /dev/null
if(function)
function();
_exit(EXIT_FAILURE);
}
if(stdin_fd)
close(stdin_p[0]);
if(stdout_fd)
close(stdout_p[1]);
if(stderr_fd)
close(stderr_p[1]);
if(stdin_fd)
*stdin_fd = stdin_p[1];
if(stdout_fd)
*stdout_fd = stdout_p[0];
if(stderr_fd)
*stderr_fd = stderr_p[0];
closed = false;
data.id = pid;
return pid;
}
Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept {
return open([&command, &path, &environment] {
auto command_c_str = command.c_str();
std::string cd_path_and_command;
if(!path.empty()) {
auto path_escaped = path;
size_t pos = 0;
// Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7
while((pos = path_escaped.find('\'', pos)) != std::string::npos) {
path_escaped.replace(pos, 1, "'\\''");
pos += 4;
}
cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links
command_c_str = cd_path_and_command.c_str();
}
if(!environment)
execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr);
else {
std::vector<std::string> env_strs;
std::vector<const char *> env_ptrs;
env_strs.reserve(environment->size());
env_ptrs.reserve(environment->size() + 1);
for(const auto &e : *environment) {
env_strs.emplace_back(e.first + '=' + e.second);
env_ptrs.emplace_back(env_strs.back().c_str());
}
env_ptrs.emplace_back(nullptr);
execle("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data());
}
});
}
void Process::async_read() noexcept {
if(data.id <= 0 || (!stdout_fd && !stderr_fd))
return;
stdout_stderr_thread = std::thread([this] {
std::vector<pollfd> pollfds;
std::bitset<2> fd_is_stdout;
if(stdout_fd) {
fd_is_stdout.set(pollfds.size());
pollfds.emplace_back();
pollfds.back().fd = fcntl(*stdout_fd, F_SETFL, fcntl(*stdout_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stdout_fd : -1;
pollfds.back().events = POLLIN;
}
if(stderr_fd) {
pollfds.emplace_back();
pollfds.back().fd = fcntl(*stderr_fd, F_SETFL, fcntl(*stderr_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stderr_fd : -1;
pollfds.back().events = POLLIN;
}
auto buffer = std::unique_ptr<char[]>(new char[config.buffer_size]);
bool any_open = !pollfds.empty();
while(any_open && (poll(pollfds.data(), static_cast<nfds_t>(pollfds.size()), -1) > 0 || errno == EINTR)) {
any_open = false;
for(size_t i = 0; i < pollfds.size(); ++i) {
if(pollfds[i].fd >= 0) {
if(pollfds[i].revents & POLLIN) {
const ssize_t n = read(pollfds[i].fd, buffer.get(), config.buffer_size);
if(n > 0) {
if(fd_is_stdout[i])
read_stdout(buffer.get(), static_cast<size_t>(n));
else
read_stderr(buffer.get(), static_cast<size_t>(n));
}
else if(n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
pollfds[i].fd = -1;
continue;
}
}
if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
pollfds[i].fd = -1;
continue;
}
any_open = true;
}
}
}
});
}
int Process::get_exit_status() noexcept {
if(data.id <= 0)
return -1;
int exit_status;
id_type pid;
do {
pid = waitpid(data.id, &exit_status, 0);
// printf("waitpid(%d, %d, 0) = %d\n", data.id, exit_status, pid);
} while(pid < 0 && errno == EINTR);
if(pid < 0 && errno == ECHILD) {
// PID doesn't exist anymore, return previously sampled exit status (or -1)
return data.exit_status;
}
else {
// Store exit status for future calls
if(exit_status >= 256)
exit_status = exit_status >> 8;
data.exit_status = exit_status;
}
{
std::lock_guard<std::mutex> lock(close_mutex);
closed = true;
}
close_fds();
return exit_status;
}
bool Process::try_get_exit_status(int &exit_status) noexcept {
if(data.id <= 0)
return false;
const id_type pid = waitpid(data.id, &exit_status, WNOHANG);
if(pid < 0 && errno == ECHILD) {
// PID doesn't exist anymore, set previously sampled exit status (or -1)
exit_status = data.exit_status;
return true;
}
else if(pid <= 0) {
// Process still running (p==0) or error
return false;
}
else {
// store exit status for future calls
if(exit_status >= 256)
exit_status = exit_status >> 8;
data.exit_status = exit_status;
}
{
std::lock_guard<std::mutex> lock(close_mutex);
closed = true;
}
close_fds();
return true;
}
void Process::close_fds() noexcept {
if(stdout_stderr_thread.joinable())
stdout_stderr_thread.join();
if(stdin_fd)
close_stdin();
if(stdout_fd) {
if(data.id > 0)
close(*stdout_fd);
stdout_fd.reset();
}
if(stderr_fd) {
if(data.id > 0)
close(*stderr_fd);
stderr_fd.reset();
}
}
bool Process::write(const char *bytes, size_t n) {
if(!open_stdin)
throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process.");
std::lock_guard<std::mutex> lock(stdin_mutex);
if(stdin_fd) {
if(::write(*stdin_fd, bytes, n) >= 0) {
return true;
}
else {
return false;
}
}
return false;
}
void Process::close_stdin() noexcept {
std::lock_guard<std::mutex> lock(stdin_mutex);
if(stdin_fd) {
if(data.id > 0)
close(*stdin_fd);
stdin_fd.reset();
}
}
void Process::kill(bool force) noexcept {
std::lock_guard<std::mutex> lock(close_mutex);
int ret = 0;
if(data.id > 0 && !closed) {
// show_all_child_process();
bool firstkill = false;
if(force) {
// 按照从最底层的进程开始杀死
list<pid_stat_fields> childprocess_list = get_child_processes(data.id);
force_kill(data.id);
usleep(10 * 1000);
for(auto &childprocess : childprocess_list) {
force_kill(childprocess.pid);
usleep(10 * 1000);
}
}
else {
kill(-data.id, SIGTERM);
}
}
}
void Process::kill(id_type id, bool force) noexcept {
if(id <= 0)
return;
if(force)
::kill(-id, SIGTERM);
else
::kill(-id, SIGINT);
}
void Process::signal(int signum) noexcept {
std::lock_guard<std::mutex> lock(close_mutex);
if(data.id > 0 && !closed) {
::kill(-data.id, signum);
}
}
} // namespace process
} // namespace iflytop

5
module.cmake

@ -24,6 +24,11 @@ set(DEP_SRC
dep/iflytopcpp/core/components/modbus/zmodbus_common.c
dep/iflytopcpp/core/components/modbus/zmodbus_master.c
dep/iflytopcpp/core/components/modbus/zmodbus_slave.c
dep/iflytopcpp/core/components/process/process_unix.cpp
dep/iflytopcpp/core/components/process/process.cpp
#
)
set(DEP_DEFINE ${DEP_DEFINE})

Loading…
Cancel
Save