ninja代码解析02-如何执行一条command
ninja如何执行一条编译指令,这个主要在builder.cc里的Build(string* err)
里实现
这里主要关注一条edge的执行
1
2
3
4
5
6
7
8
9
bool Builder::StartEdge(Edge* edge, string* err) {
// ... previous code
// start command computing and run it
if (!command_runner_->StartCommand(edge)) {
err->assign("command '" + edge->EvaluateCommand() + "' failed.");
return false;
}
return true;
}
这里会开始一条edge的build,具体调用是在RealCommandRunner里的StartCommand
1
2
3
4
5
6
7
8
9
bool RealCommandRunner::StartCommand(Edge* edge) {
string command = edge->EvaluateCommand();
Subprocess* subproc = subprocs_.Add(command, edge->use_console());
if (!subproc)
return false;
subproc_to_edge_.insert(make_pair(subproc, edge));
return true;
}
这里一条edge的编译command会被算出来,然后起一个subprocess去run,跟py里的subprocess run查不多,之后会起一个Subprocess加到ProcessSet里
1
2
3
4
5
6
7
8
9
Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
Subprocess *subprocess = new Subprocess(use_console);
if (!subprocess->Start(this, command)) {
delete subprocess;
return 0;
}
running_.push_back(subprocess);
return subprocess;
}
这里可以开始看subprocess的头文件了, 这里的subprocess设计如此,需要用户去关注fd的可读事件来标志process的finish,ninja里为了通用,在unix下都是用的ppoll或者pselect来实现的。理论上讲,这里的并发数受到cpu core的限制,不会有太大的问题。
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
33
34
35
36
37
38
39
40
/// Subprocess wraps a single async subprocess. It is entirely
/// passive: it expects the caller to notify it when its fds are ready
/// for reading, as well as call Finish() to reap the child once done()
/// is true.
struct Subprocess {
~Subprocess();
/// Returns ExitSuccess on successful process exit, ExitInterrupted if
/// the process was interrupted, ExitFailure if it otherwise failed.
ExitStatus Finish();
bool Done() const;
const std::string& GetOutput() const;
private:
Subprocess(bool use_console);
bool Start(struct SubprocessSet* set, const std::string& command);
void OnPipeReady();
std::string buf_;
#ifdef _WIN32
/// Set up pipe_ as the parent-side pipe of the subprocess; return the
/// other end of the pipe, usable in the child process.
HANDLE SetupPipe(HANDLE ioport);
HANDLE child_;
HANDLE pipe_;
OVERLAPPED overlapped_;
char overlapped_buf_[4 << 10];
bool is_reading_;
#else
int fd_;
pid_t pid_;
#endif
bool use_console_;
friend struct SubprocessSet;
};
随后具体看Start(struct SubprocessSet* set, const std::string& command)
的这个调用,这里设置process的action + attribute, posix_spawn
来创建一个新的process 输出会被写道pipe[0]里,此时fd_被记录成pipe[0], 这个fd_在结束后是可读的
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
bool Subprocess::Start(SubprocessSet* set, const string& command) {
int output_pipe[2];
if (pipe(output_pipe) < 0)
Fatal("pipe: %s", strerror(errno));
fd_ = output_pipe[0];
#if !defined(USE_PPOLL)
// If available, we use ppoll in DoWork(); otherwise we use pselect
// and so must avoid overly-large FDs.
if (fd_ >= static_cast<int>(FD_SETSIZE))
Fatal("pipe: %s", strerror(EMFILE));
#endif // !USE_PPOLL
// exec前关闭fd,防止fd泄漏
SetCloseOnExec(fd_);
posix_spawn_file_actions_t action;
int err = posix_spawn_file_actions_init(&action);
if (err != 0)
Fatal("posix_spawn_file_actions_init: %s", strerror(err));
// output[0]是读,子进程不需要读,关闭
err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
if (err != 0)
Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
posix_spawnattr_t attr;
err = posix_spawnattr_init(&attr);
if (err != 0)
Fatal("posix_spawnattr_init: %s", strerror(err));
short flags = 0;
// 这里是设置process的sig掩码
flags |= POSIX_SPAWN_SETSIGMASK;
err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
if (err != 0)
Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
// Signals which are set to be caught in the calling process image are set to
// default action in the new process image, so no explicit
// POSIX_SPAWN_SETSIGDEF parameter is needed.
if (!use_console_) {
// Put the child in its own process group, so ctrl-c won't reach it.
flags |= POSIX_SPAWN_SETPGROUP;
// No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
// Open /dev/null over stdin.
err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
0);
if (err != 0) {
Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
}
// 写端复制到标准输出和错误,然后关闭原始输出写断
err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
if (err != 0)
Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
if (err != 0)
Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
if (err != 0)
Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
// In the console case, output_pipe is still inherited by the child and
// closed when the subprocess finishes, which then notifies ninja.
}
//
#ifdef POSIX_SPAWN_USEVFORK
// POSIX_SPAWN_USEVFORK是一个标志位,值为0x40
// 它指示系统在创建子进程时使用vfork()而非fork()
// vfork()比fork()更高效,因为它不会复制父进程的内存页面
// 而是共享父进程的地址空间,直到exec调用
flags |= POSIX_SPAWN_USEVFORK;
#endif
err = posix_spawnattr_setflags(&attr, flags);
if (err != 0)
Fatal("posix_spawnattr_setflags: %s", strerror(err));
const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
const_cast<char**>(spawned_args), environ);
if (err != 0)
Fatal("posix_spawn: %s", strerror(err));
err = posix_spawnattr_destroy(&attr);
if (err != 0)
Fatal("posix_spawnattr_destroy: %s", strerror(err));
err = posix_spawn_file_actions_destroy(&action);
if (err != 0)
Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
close(output_pipe[1]);
return true;
}
随后要自己处理SubprocessSet::DoWork(),走一个event loop去处理完成的process,配合之前说的被动处理fd。把runing queue的fd放进finished queue
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
bool SubprocessSet::DoWork() {
vector<pollfd> fds;
nfds_t nfds = 0;
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i) {
int fd = (*i)->fd_;
if (fd < 0)
continue;
pollfd pfd = { fd, POLLIN | POLLPRI, 0 };
fds.push_back(pfd);
++nfds;
}
interrupted_ = 0;
int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
if (ret == -1) {
if (errno != EINTR) {
perror("ninja: ppoll");
return false;
}
return IsInterrupted();
}
HandlePendingInterruption();
if (IsInterrupted())
return true;
nfds_t cur_nfd = 0;
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ) {
int fd = (*i)->fd_;
if (fd < 0)
continue;
assert(fd == fds[cur_nfd].fd);
if (fds[cur_nfd++].revents) {
(*i)->OnPipeReady();
if ((*i)->Done()) {
finished_.push(*i);
i = running_.erase(i);
continue;
}
}
++i;
}
return IsInterrupted();
}
Subprocess + SubprocessSet还有很多信号的处理,可以具体看看
This post is licensed under CC BY 4.0 by the author.