啟動環境
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=</path/to/executable>\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=</path/to/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<string> 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<brillo::CrosConfig> cros_config =
base::MakeUnique<brillo::CrosConfig>();
if (!cros_config->Init())
cros_config = nullptr;
bool is_developer_end_user = false;
map<string, string> env_vars;
vector<string> 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<BrowserJob>(
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<SessionManagerService> 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();