在前面一篇文章中我们了解到了Pause镜像在Kubernetes中管理的最小单元Pod中的应用,这篇文章中我们来对Pause的源码进行解读以增强理解。
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include
#define STRINGIFY(x) #x
#define VERSION_STRING(x) STRINGIFY(x)
#ifndef VERSION
#define VERSION HEAD
#endif
static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}
static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; ++i) {
if (!strcasecmp(argv[i], "-v")) {
printf("pause.c %s\n", VERSION_STRING(VERSION));
return 0;
}
}
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first process\n");
if (sigaction(SIGINT, &(struct sigaction){
.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){
.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){
.sa_handler = sigreap,
.sa_flags = SA_NOCLDSTOP},
NULL) < 0)
return 3;
for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminated\n");
return 42;
}
这是一个不复杂的C语言程序,有一些信号量处理经验的开发者能很容易的理解,加上注释也只有68行。核心的代码是如下两行:
for (;;)
pause();
这是一个无限循环,至于气候的fprintf是例行的写法,42这个宇宙的本质的答案再次以硬编码的形式霸气地出现在一个只有68行代码的程序中,这并不是我们关注的重点。这个无限循环中出现的pause函数是什么?
pause函数是unistd.h中用于处理信号量的,首先我们通过一个简单的示例来理解一下pause的使用方式:
liumiaocn:pause liumiao$ cat testpause.c
#include
#include
#include
#define TIME_PERIOD 3
void signal_handler(int period) {
printf("sigal handler (thinking) begins ...\n");
signal(SIGALRM,signal_handler);
alarm(period);
printf("sigal handler (thinking) ends ...");
}
int main(void)
{
printf("Guess what LiuMiao wana tell you? ...\n");
signal_handler(TIME_PERIOD);
printf("\npause begins ...\n");
printf("waiting ...\n\n");
pause();
printf("\npause ends ...\n");
printf("## Surprise: Nothing\n");
return 0;
}
liumiaocn:pause liumiao$
代码说明:pause的作用是一直等待直到有信号量将其唤醒,示例中的signal_handler函数起到了这个作用,唤醒之前猜测问题,唤醒之后告诉答案。编译环境如下所示:
liumiaocn:pause liumiao$ gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.16)
Target: x86_64-apple-darwin19.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
liumiaocn:pause liumiao$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.2
BuildVersion: 19C57
liumiaocn:pause liumiao$
编译
liumiaocn:pause liumiao$ gcc testpause.c -o testpause
liumiaocn:pause liumiao$ ls
testpause testpause.c
liumiaocn:pause liumiao$
执行结果如下所示:
liumiaocn:pause liumiao$ ./testpause
Guess what LiuMiao wana tell you? ...
sigal handler (thinking) begins ...
sigal handler (thinking) ends ...
pause begins ...
waiting ...
sigal handler (thinking) begins ...
sigal handler (thinking) ends ...
pause ends ...
## Surprise: Nothing
liumiaocn:pause liumiao$
结合Pause的源码不难理解Pause容器大部分时间所起到的作用,就是等待唤醒,唤醒之后继续沉睡直到下次唤醒,周而复始永不停息,这也是在前面的文章中我们看到的使用docker inspect可以看到pause容器的CPUPeriod被设定为0, 结合源码也就比较容易理解了,绝大多数时间都在睡觉的一个容器。
Linux中Pid为1的进程,占据此进程的目前主要有init和systemd,比如CentOS或者RHEL下面可能是这样的
[root@host131 Pod]# ps -ef |grep systemd |grep deserialize
root 1 0 0 19:51 ? 00:00:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
[root@host131 Pod]#
而pause运行的时候,需要其pid为1,但是注意如果不为1并不是一个错误,不过没有必要过于纠结这一点,了解到其设计的思路是希望pause启动的作用即可。
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first process\n");
这是一个非常基础的信号量的使用控制方法
if (sigaction(SIGCHLD, &(struct sigaction){
.sa_handler = sigreap,
.sa_flags = SA_NOCLDSTOP},
NULL) < 0)
return 3;
sigaction会捕获SIGCHLD信号,通过sigreap函数对进行进行处理
static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
而waitpid中-1则表示等待子进程退出,而WNOHANG的指定表示 子进程未结束时不阻塞。所以虽然代码很少,还是能够起到很重要的作用的。
Pause分担了init进行的作用,包括僵尸进程的处理,而这些都是Kubernetes所提供的平台基础功能的一部分,试想如果僵尸进程的处理需要Pod中运行的其他容器比如nginx来实现,而nginx本身又不提供这样的功能将会多么麻烦。