背景
最近遇到桌面系统中,开机动画卡顿的问题,第一印象感觉好像是显卡驱动或者是硬件问题,绘图慢,从而导致卡顿。
但是,打开plymouth相关的调试开关后并没有发现明显的错误打印,甚是疑惑。于是,对开机动画组件plymouth做了一番研究,其实就是看代码了,搞明白其原理后,才明白原来是这么回事儿。
开机动画相关概念
开机动画就是在开机后,系统启动过程中看到的一些动画显示,通常的桌面系统中(服务器系统不一定),都会有这样的动画,目的是不想让用户看到具体的启动过程,另一方面也更美观。
Linux中,开机动画基本都是用开源组件plymouth,没有研究名字的来源,有点怪怪的~
plymouth相关原理
终于到正题了,plymouth总体来说,比我预想中的复杂,并不是简单的一些动画而已,其功能还比较强大,而且跟systemd绑在一起,有点过度设计的嫌疑~
plymouth整体分两个主要部分(还有一些边角的功能,不做分析了),服务端和客户端,典型的C/S模型。服务端和客户端直接通过socket通信。
- 服务端。是一个后台守护进程plymouthd,用于处理请求,请求种类有很多,比如典型的update、quit等。服务端通过epoll监控相关socket(也有管道),监听来自客户端的信息。
- 客户端。客户端可以多种多样,典型的客户端有:plymouth程序、systemd。客户端通过socket(也有管道)与服务端建立连接,并通过socket(也有管道)发送具体的请求。
plymouthd服务端
如前面所说,plymouthd守护进程作为开机动画的服务端,是最核心的部分,这节主要分析plymouthd的相关原理。
主函数流程
plymouthd服务端的主函数入口为src/main.c文件中main()函数,主要流程为:
- 解析参数
- 创建后台守护进程
- 初始化环境
- 启动服务器(socket),并监听(listen)来自客户端的连接消息
- 从cache文件中获取每个服务对应的进度信息
- 进入消息循环
代码如下(含注释):
/*plymouthd服务的主函数入口*/
int
main (int argc,
char **argv)
{
state_t state = { 0 };
int exit_code;
bool should_help = false;
bool no_daemon = false;
bool debug = false;
bool attach_to_session;
ply_daemon_handle_t *daemon_handle = NULL;
char *mode_string = NULL;
char *kernel_command_line = NULL;
char *tty = NULL;
/*参数解析器*/
state.command_parser = ply_command_parser_new ("plymouthd", "Splash server");
/*创建默认消息循环*/
state.loop = ply_event_loop_get_default ();
/*参数*/
ply_command_parser_add_options (state.command_parser,
"help", "This help message", PLY_COMMAND_OPTION_TYPE_FLAG,
"attach-to-session", "Redirect console messages from screen to log", PLY_COMMAND_OPTION_TYPE_FLAG,
"no-daemon", "Do not daemonize", PLY_COMMAND_OPTION_TYPE_FLAG,
"debug", "Output debugging information", PLY_COMMAND_OPTION_TYPE_FLAG,
"debug-file", "File to output debugging information to", PLY_COMMAND_OPTION_TYPE_STRING,
"mode", "Mode is one of: boot, shutdown", PLY_COMMAND_OPTION_TYPE_STRING,
"pid-file", "Write the pid of the daemon to a file", PLY_COMMAND_OPTION_TYPE_STRING,
"kernel-command-line", "Fake kernel command line to use", PLY_COMMAND_OPTION_TYPE_STRING,
"tty", "TTY to use instead of default", PLY_COMMAND_OPTION_TYPE_STRING,
NULL);
/*解析参数*/
if (!ply_command_parser_parse_arguments (state.command_parser, state.loop, argv, argc))
{
char *help_string;
help_string = ply_command_parser_get_help_string (state.command_parser);
ply_error_without_new_line ("%s", help_string);
free (help_string);
return EX_USAGE;
}
/*获取参数*/
ply_command_parser_get_options (state.command_parser,
"help", &should_help,
"attach-to-session", &attach_to_session,
"mode", &mode_string,
"no-daemon", &no_daemon,
"debug", &debug,
"debug-file", &debug_buffer_path,
"pid-file", &pid_file,
"tty", &tty,
"kernel-command-line", &kernel_command_line,
NULL);
if (should_help)
{
char *help_string;
help_string = ply_command_parser_get_help_string (state.command_parser);
if (argc < 2)
fprintf (stderr, "%s", help_string);
else
printf ("%s", help_string);
free (help_string);
return 0;
}
/*是否开启debug选项,开启后能有详细的日志*/
if (debug && !ply_is_tracing ())
ply_toggle_tracing ();
if (mode_string != NULL)
{
if (strcmp (mode_string, "shutdown") == 0)
state.mode = PLY_MODE_SHUTDOWN;
else if (strcmp (mode_string, "updates") == 0)
state.mode = PLY_MODE_UPDATES;
else
state.mode = PLY_MODE_BOOT;
free (mode_string);
}
if (tty != NULL)
{
state.default_tty = tty;
}
if (kernel_command_line != NULL)
{
strncpy (state.kernel_command_line, kernel_command_line, sizeof (state.kernel_command_line));
state.kernel_command_line[sizeof (state.kernel_command_line) - 1] = '\0';
state.kernel_command_line_is_set = true;
}
if (geteuid () != 0)
{
ply_error ("plymouthd must be run as root user");
return EX_OSERR;
}
chdir ("/");
signal (SIGPIPE, SIG_IGN);
if (! no_daemon)
{
/*创建后台守护进程,前台进程退出*/
daemon_handle = ply_create_daemon ();
if (daemon_handle == NULL)
{
ply_error ("plymouthd: cannot daemonize: %m");
return EX_UNAVAILABLE;
}
}
if (debug)
debug_buffer = ply_buffer_new ();
signal (SIGABRT, on_crash);
signal (SIGSEGV, on_crash);
/* before do anything we need to make sure we have a working
* environment.
*/
/*初始化环境*/
if (!initialize_environment (&state))
{
if (errno == 0)
{
if (daemon_handle != NULL)
ply_detach_daemon (daemon_handle, 0);
return 0;
}
ply_error ("plymouthd: could not setup basic operating environment: %m");
if (daemon_handle != NULL)
ply_detach_daemon (da