《Linux 工作原理》一书中说一般的关闭过程(独立于 init 系统)是这样的:
- init 要求每个进程彻底关闭。
- 如果进程在一段时间后没有响应,init 将杀死它,首先尝试 TERM 信号。
- 如果 TERM 信号不起作用,init 将对任何落后者使用 KILL 信号。
- 系统将系统文件锁定到位并为关闭做好其他准备。
- 系统卸载除根之外的所有文件系统。
- 系统重新挂载了根文件系统只读。
- 系统将所有缓冲数据写入文件系统与同步程序。
- 最后一步是通过reboot(2)系统调用告诉内核重新启动或停止。这可以通过 init 或辅助程序(例如重新启动、暂停或关闭电源)来完成。
如果文件系统是只读的,sync 如何写入其缓冲区?
答案1
您感到惊讶是对的:这个命令没有意义。如果一本书这样表述,那就是草率且具有误导性的。
卸载文件系统或以只读方式安装它会将所有数据写入磁盘。当umount
命令ormount -o remount,ro
返回时,所有数据都被写入磁盘并且sync
没有什么可做的。在之前调用是没有意义的sync
(数据无论如何都会被umount操作写入),在之后调用也是没有意义的(它不会做任何事情)。
我认为在一些古老的 Unix 系统中这不是真的,你必须调用sync
前卸载。之后调用它仍然没有意义。
如果您超越文件系统,可能会在某些情况下sync
执行某些操作。例如,我认为在 Linux 上sync
可以确保 RAID 阵列的元数据写入磁盘。即使在没有任何读写文件系统的情况下,这也是有用的。
答案2
重新挂载 fs 只读会阻止进程中任何文件级写入请求和 rw 模式下的 open() 调用,因此无法进一步修改数据和 fs 结构。缓冲位于块设备驱动程序和文件系统驱动程序之间,因此如果系统有任何脏缓冲区,则应将它们写入底层介质。
典型的堆栈如下所示:
- 过程
- 内核文件io API
fs
司机,例如ext3fs
blkdev
抽象层、API、许多有用的原语、默认行为等内核的一部分。此外,该层还管理缓冲区和磁盘缓存,并提供与内核的交换。- 块设备驱动程序,例如
scsi
linux子系统。 - 储存设备
不同级别也可能存在循环。例如,文件可以用作支持存储、LUKS
设备加密等的循环设备
答案3
以下是执行关闭操作的部分代码(System V 风格实现):
/*
* Kill all processes, call /etc/init.d/halt (if present)
*/
void fastdown()
{
int do_halt = (down_level[0] == '0');
int i;
#if 0
char cmd[128];
char *script;
/*
* Currently, the halt script is either init.d/halt OR rc.d/rc.0,
* likewise for the reboot script. Test for the presence
* of either.
*/
if (do_halt) {
if (access(HALTSCRIPT1, X_OK) == 0)
script = HALTSCRIPT1;
else
script = HALTSCRIPT2;
} else {
if (access(REBOOTSCRIPT1, X_OK) == 0)
script = REBOOTSCRIPT1;
else
script = REBOOTSCRIPT2;
}
#endif
/* First close all files. */
for(i = 0; i < 3; i++)
if (!isatty(i)) {
close(i);
open("/dev/null", O_RDWR);
}
for(i = 3; i < 20; i++) close(i);
close(255);
/* First idle init. */
if (kill(1, SIGTSTP) < 0) {
fprintf(stderr, "shutdown: can't idle init: %s.\r\n", strerror(errno));
exit(1);
}
/* Kill all processes. */
fprintf(stderr, "shutdown: sending all processes the TERM signal...\r\n");
kill(-1, SIGTERM);
sleep(sltime ? atoi(sltime) : 3);
fprintf(stderr, "shutdown: sending all processes the KILL signal.\r\n");
(void) kill(-1, SIGKILL);
#if 0
/* See if we can run /etc/init.d/halt */
if (access(script, X_OK) == 0) {
spawn(1, cmd, "fast", NULL);
fprintf(stderr, "shutdown: %s returned - falling back "
"on default routines\r\n", script);
}
#endif
/* script failed or not present: do it ourself. */
sleep(1); /* Give init the chance to collect zombies. */
/* Record the fact that we're going down */
write_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");
/* This is for those who have quota installed. */
#if defined(ACCTON_OFF)
# if (ACCTON_OFF > 1) && (_BSD_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500))
/* This is an alternative way to disable accounting, saving a fork() */
if (acct(NULL))
fprintf(stderr, "shutdown: can not stop process accounting: %s.\r\n", strerror(errno));
# elif (ACCTON_OFF > 0)
spawn(1, "accton", "off", NULL);
# else
spawn(1, "accton", NULL);
# endif
#endif
spawn(1, "quotaoff", "-a", NULL);
sync();
fprintf(stderr, "shutdown: turning off swap\r\n");
spawn(0, "swapoff", "-a", NULL);
fprintf(stderr, "shutdown: unmounting all file systems\r\n");
spawn(0, "umount", "-a", NULL);
/* We're done, halt or reboot now. */
if (do_halt) {
fprintf(stderr, "The system is halted. Press CTRL-ALT-DEL "
"or turn off power\r\n");
init_reboot(BMAGIC_HALT);
exit(0);
}
fprintf(stderr, "Please stand by while rebooting the system.\r\n");
init_reboot(BMAGIC_REBOOT);
exit(0);
}
正如您首先看到的进程终止部分是,然后我们有:
sync();
fprintf(stderr, "shutdown: turning off swap\r\n");
spawn(0, "swapoff", "-a", NULL);
fprintf(stderr, "shutdown: unmounting all file systems\r\n");
spawn(0, "umount", "-a", NULL);
它使用将数据写入磁盘sync
。然后关闭交换并卸载所有文件系统。然后发生实际的停止或重新启动。
sync
手册页的描述:
sync() 会导致对文件系统元数据和缓存文件数据的所有挂起修改写入底层文件系统。
这本书可能有点旧,或者解释了关闭的其他实现。阅读代码和手册页也是了解 Linux 工作原理的一个很好的方法。