启动环境
session_manager
在文件/etc/init/ui.conf
中通过UpStart启动,具体启动内容为:
env UI_LOG_DIR=/var/log/ui
env UI_LOG_FILE=ui.LATEST
env CHROME_COMMAND_FLAG
exec session_manager \
"${CHROME_COMMAND_FLAG}" \
>"${UI_LOG_DIR}/${UI_LOG_FILE}" 2>&1
相关定义
session_manager
启动代码位于源码目录src/platform2/login_manager/session_manager_main.cc
:
开关定义
src/platform2/login_manager/session_manager_main.cc
中首先为Chrome
运行定义了部分开关:
namespace switches {
// Name of the flag that contains the command for running Chrome.
static const char kChromeCommand[] = "chrome-command";
static const char kChromeCommandDefault[] = "/opt/google/chrome/chrome";
// Name of the flag that contains the path to the file which disables restart of
// managed jobs upon exit or crash if the file is present.
static const char kDisableChromeRestartFile[] = "disable-chrome-restart-file";
// The default path to this file.
static const char kDisableChromeRestartFileDefault[] =
"/run/disable_chrome_restart";
// Flag that causes session manager to show the help message and exit.
static const char kHelp[] = "help";
// The help message shown if help flag is passed to the program.
static const char kHelpMessage[] =
"\nAvailable Switches: \n"
" --chrome-command=\n"
" Path to the Chrome executable. Split along whitespace into arguments\n"
" (to which standard Chrome arguments will be appended); a value like\n"
" \"/usr/local/bin/strace /path/to/chrome\" may be used to wrap Chrome "
"in\n"
" another program. (default: /opt/google/chrome/chrome)\n"
" --disable-chrome-restart-file=\n"
" Magic file that causes this program to stop restarting the\n"
" chrome binary and exit. (default: /run/disable_chrome_restart)\n";
} // namespace switches
变量定义
src/platform2/login_manager/session_manager_main.cc
中还定义了如下这些变量:
// Directory in which per-boot metrics flag files will be stored.
static const char kFlagFileDir[] = "/run/session_manager";
// Hang-detection magic file and constants.
static const char kHangDetectionFlagFile[] = "enable_hang_detection";
static const uint32_t kHangDetectionIntervalDefaultSeconds = 60;
static const uint32_t kHangDetectionIntervalShortSeconds = 5;
// Time to wait for children to exit gracefully before killing them
// with a SIGABRT.
static const int kKillTimeoutDefaultSeconds = 3;
static const int kKillTimeoutLongSeconds = 12;
其中目录/run/session_manager
中存储了与启动相关的一些信息,该目录下包含三个文件:
-
logged_in
: 用于表明本次启动是否成功登陆。值为1
则表示登陆成功。 -
machine-info
: 用于启动过程中写入机器信息,信息如下:"mlb_serial_number"="QCC0C81LA51900261" "initial_locale"="en-US" "initial_timezone"="America/Los_Angeles" "keyboard_layout"="xkb:us::eng" "region"="us" "serial_number"="F5N0CX222377209" "___ro_rw_delimiter___"="___RW_VPD_below___" "gbind_attribute"="1873ca856c1b05ca21a0a6c86f1bcc96fa97d8d473d572ba377c2039b101df1cd155f4c9" "ubind_attribute"="3ef20362e87e105a655b2510c70bc8ba97ef3d358ed16e0ace0389c263dcf3a223e9140e" "ActivateDate"="2015-30" root_disk_serial_number=0x017bdd97
1 per_boot_flag
: 启动度量信息,通常为空。
执行分析
初始化处理
session_manager
主函数在文件src/platform2/login_manager/session_manager_main.cc
中,进入主函数,首先做了如下几件事:
- 声明退出函数;
- 初始化命令行参数;
- 获取当前进程的命令行参数;
- 初始化日志子系统;
对应代码为:
base::AtExitManager exit_manager;
base::CommandLine::Init(argc, argv);
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogHeader);
超级进程设置
使用系统函数prctl
的参数PR_SET_CHILD_SUBREAPER
将当前进程设置为subreaper
进程,之后该进程就可以像进程ID号为1的进程那样,递归收养所有由该进程派生出的孤儿进程,该功能在Linux Kernel 3.4之后加入,对应代码如下:
// Allow waiting for all descendants, not just immediate children
if (::prctl(PR_SET_CHILD_SUBREAPER, 1))
PLOG(ERROR) << "Couldn't set child subreaper";
处理非必要参数
如果命令行参数指定为--help
,则打印帮助信息然后退出即可:
if (cl->HasSwitch(switches::kHelp)) {
LOG(INFO) << switches::kHelpMessage;
return 0;
}
处理传入的Chrome参数
接下来就是处理传入Chrome所需的命令行参数:
// Parse the base Chrome command.
string command_flag(switches::kChromeCommandDefault);
if (cl->HasSwitch(switches::kChromeCommand))
command_flag = cl->GetSwitchValueASCII(switches::kChromeCommand);
vector command =
base::SplitString(command_flag, base::kWhitespaceASCII,
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
这里的操作具体有:默认chrome
的二进制文件路径为/opt/google/chrome/chrome
,但是如果有设置开关chrome-command
的值,则将其对应的值设置为二进制文件路径。接下来通过、将空白字符转义、保留空白和去除空串的方式,对命令行进行正规化处理。
设置Chrome命令行参数
然后是设置session_manager
所启用的Chrome
的命令行参数:
// Set things up for running Chrome.
std::unique_ptr cros_config =
base::MakeUnique();
if (!cros_config->Init())
cros_config = nullptr;
bool is_developer_end_user = false;
map env_vars;
vector args;
uid_t uid = 0;
PerformChromeSetup(
cros_config.get(), &is_developer_end_user, &env_vars, &args, &uid);
command.insert(command.end(), args.begin(), args.end());
最终设置的命令行参数如下所示:
/opt/google/chrome/chrome
--ui-prioritize-in-gpu-process
--use-gl=egl
--gpu-sandbox-failures-fatal=yes
--enable-logging
--log-level=1
--use-cras
--enable-wayland-server
--user-data-dir=/home/chronos
--max-unused-resource-memory-usage-percentage=5
--system-developer-mode
--login-profile=user
--has-chromeos-keyboard
--enable-prefixed-encrypted-media
--enable-consumer-kiosk
--enterprise-enrollment-initial-modulus=15
--enterprise-enrollment-modulus-limit=19
--login-manager
--first-exec-after-boot
--vmodule=*chromeos/login/*=1,auto_enrollment_controller=1,*plugin*=2,*zygote*=1,*/ui/ozone/*=1,*/ui/display/manager/chromeos/*=1,power_button_observer=2,webui_login_view=2,lock_state_controller=2,webui_screen_locker=2,screen_locker=2
其中核心函数PerformChromeSetup
实现在文件src/platform2/login_manager/chrome_setup.cc
。
声明系统核心调用
声明一个SystemUtilsImpl
类型的变量system
用于系统调用和操作:
// Shim that wraps system calls, file system ops, etc.
SystemUtilsImpl system;
检查是否停止session_manager管理浏览器进程
检查是否设置了手动停止或启动浏览器时,session_manager
保持运行状态,也就是说session_manager
在此时不再管理浏览器进程:
// Checks magic file that causes the session_manager to stop managing the
// browser process. Devs and tests can use this to keep the session_manager
// running while stopping and starting the browser manaually.
string magic_chrome_file =
cl->GetSwitchValueASCII(switches::kDisableChromeRestartFile);
if (magic_chrome_file.empty())
magic_chrome_file.assign(switches::kDisableChromeRestartFileDefault);
FileChecker checker((base::FilePath(magic_chrome_file))); // So vexing!
该文件默认为/var/run/disable_chrome_restart
,如果该文件存在则表示启用该项设置。
创建度量信息保存目录
// Used to report various metrics around user type (guest vs non), dev-mode,
// and policy/key file status.
base::FilePath flag_file_dir(kFlagFileDir);
if (!base::CreateDirectory(flag_file_dir))
PLOG(FATAL) << "Cannot create flag file directory at " << kFlagFileDir;
LoginMetrics metrics(flag_file_dir);
默认的度量信息保存目录为/run/session_manager/
。
检查是否停止session_manager检查浏览器存活状态
默认情况下,session_manager
会周期性检查浏览器是否存活,在开发模式下会导致部分问题,因为调试浏览器会导致该功能不可用,因此可以通过创建一个标签文件的方式来禁用该功能,默认情况下该文件目录为/run/session_manager/enable_hang_detection
:
// The session_manager supports pinging the browser periodically to check that
// it is still alive. On developer systems, this would be a problem, as
// debugging the browser would cause it to be aborted. Override via a
// flag-file is allowed to enable integration testing.
bool enable_hang_detection = !is_developer_end_user;
uint32_t hang_detection_interval = kHangDetectionIntervalDefaultSeconds;
if (base::PathExists(flag_file_dir.Append(kHangDetectionFlagFile)))
hang_detection_interval = kHangDetectionIntervalShortSeconds;
修改浏览器平滑退出检测时间
对于包含旋转磁盘的平台来说,Chrome
会花费更多的时间来关闭,在这种情况下,需要知道是什么原因导致关闭花费了更多的时间,也因此在杀次Chrome
并触发报告之前需要等待更久,这里提出了如何修改该事件的方法:
// On platforms with rotational disks, Chrome takes longer to shut down. As
// such, we need to change our baseline assumption about what "taking too long
// to shutdown" means and wait for longer before killing Chrome and triggering
// a report.
int kill_timeout = kKillTimeoutDefaultSeconds;
if (BootDeviceIsRotationalDisk())
kill_timeout = kKillTimeoutLongSeconds;
LOG(INFO) << "Will wait " << kill_timeout << "s for graceful browser exit.";
正规化命令行
这里对命令行参数和调用者的UID进行了整合,为执行最终的命令做好了最后的准备:
// This job encapsulates the command specified on the command line, and the
// UID that the caller would like to run it as.
auto browser_job = base::MakeUnique(
command, env_vars, uid, &checker, &metrics, &system);
bool should_run_browser = browser_job->ShouldRunBrowser();
创建消息循环
基于brillo
下的BaseMessageLoop
创建消息循环:
base::MessageLoopForIO message_loop;
brillo::BaseMessageLoop brillo_loop(&message_loop);
brillo_loop.SetAsCurrent();
创建session_manager服务
创建的session_manager
服务对应SessionManagerService
类的实现:
scoped_refptr manager = new SessionManagerService(
std::move(browser_job),
uid,
kill_timeout,
enable_hang_detection,
base::TimeDelta::FromSeconds(hang_detection_interval),
&metrics,
&system);
执行消息循环
初始化管理其后执行消息循环:
if (manager->Initialize()) {
// Allows devs to start/stop browser manually.
if (should_run_browser) {
brillo_loop.PostTask(
FROM_HERE, base::Bind(&SessionManagerService::RunBrowser, manager));
}
// Returns when brillo_loop.BreakLoop() is called.
brillo_loop.Run();
}
执行清理动作
在退出循环后,还需要执行部分清理工作:
manager->Finalize();
退出服务
运行的最后退出session_manager
服务:
LOG_IF(WARNING, manager->exit_code() != SessionManagerService::SUCCESS)
<< "session_manager exiting with code " << manager->exit_code();
return manager->exit_code();