简体   繁体   English

在C程序中调用的Linux重启函数会导致程序在磁盘上创建文件丢失

[英]Linux reboot function called in C program causes file loss created by the program on disk

I have developped a C program (Linux), this program create a new file and write into, after that it reboots the PC. 我已经开发了一个C程序(Linux),这个程序创建一个新文件并写入,然后重新启动PC。

After reboot, I have lost the file created by my program. 重启后,我丢失了程序创建的文件。 When I deactivate reboot function, the file created by my program is still present. 当我停用重启功能时,我的程序创建的文件仍然存在。

This behaviour is seen with Linux: - OpenWrt (Backfire 10.03) on VirtualBox (filesystem ext2) - Linux (Ubuntu) (filesystem ext4) 在Linux上可以看到这种行为: - VirtualBox上的OpenWrt(Backfire 10.03)(文件系统ext2) - Linux(Ubuntu)(文件系统ext4)

Have you an explication for this behavior and how can I fix it? 您是否已解释此行为以及如何解决此问题?

#include <stdio.h>
#include <sys/reboot.h>

int main ()
{
    FILE    *pFile;
    char    mybuffer[80];

    pFile = fopen ("/home/user/Desktop/example.txt","w");
    if (pFile == NULL) perror ("Error opening file");
    else
    {
        fputs ("test",pFile);
        fclose (pFile);
    }
    rename("/home/user/Desktop/example.txt","/home/user/Desktop/example123.txt");
    reboot(RB_AUTOBOOT);
    return 0;
}

The man page for fclose says: fclose的手册页说:

Note that fclose() only flushes the user space buffers provided by the C library. 请注意,fclose()仅刷新C库提供的用户空间缓冲区。 To ensure that the data is physically stored on disk the kernel buffers must be flushed too, for example, with sync(2) or fsync(2). 为确保数据物理存储在磁盘上,必须刷新内核缓冲区,例如,使用sync(2)或fsync(2)。

Which means that you need to call fsync before closing the file descriptor. 这意味着您需要在关闭文件描述符之前调用fsync

The immediate problem is, that you don't sync the file before doing the reboot. 当前的问题是,在重新启动之前不要同步文件。 The actual problem is, that you call the reboot syscall directly, without regard for what else is happening on the system. 实际问题是,您直接调用reboot syscall,而不考虑系统上发生了什么。 What you do is very similar to simply pressing the HW reset button; 你所做的与简单地按下硬件复位按钮非常相似; you just give the kernel the chance to do a little bit of cleanup, but then everything is killed the hard way. 你只是让内核有机会做一些清理工作,但随后一切都被杀死了。 This is a dead sure way to eventually corrupt filesystems and file structures. 这是最终破坏文件系统和文件结构的可靠方法。 Don't do this! 不要这样做! .

Instead you should ask the init system to perform a gracefull reboot. 相反,您应该要求init系统执行gracefull重新启动。 Calling the reboot syscall requires privileged access. 调用reboot syscall需要特权访问权限。 So you can just ask the init system to reboot as well. 所以你也可以要求init系统重启。 On most systems there's a symlink /sbin/reboot that points to the program that will initiate a sane reboot if called through that symlink. 在大多数系统上都有一个符号链接/sbin/reboot指向程序,如果通过该符号链接调用该程序将启动理智的重启。 Hence I recommend you replace your dirty reboot(RB_AUTOBOOT) with (note the double specification of "/sbin/reboot" in execlp – this is important). 因此,我建议您更换脏reboot(RB_AUTOBOOT) (注意execlp中的"/sbin/reboot"的双重规范 - 这很重要)。

pid_t reboot_pid;
if( 0 == (reboot_pid = fork()) ) {
    execlp("/sbin/reboot", "/sbin/reboot", NULL);
    exit(1); /* never reached if execlp succeeds. */
}
if( -1 == reboot_pid ) {
    /* fork error... deal with it somehow */
}
int reboot_status;
waitpid(reboot_pid, &reboot_status, 0);
if( !WIFEXITED(reboot_status) ) {
    /* reboot process did not exit sanely... deal with it somehow */
}
if( 0 != WIFEXITSTATUS(reboot_status) ) {
    /* reboot process exited with error;
     * most likely the user lacks the required privileges */
}
else {
    fputs("reboot call sucessfull -- system is about to shutdown.");
    /* The init system is now shutting down the system. It will signals all
     * programs to terminate by sending SIGTERM, followed by SIGKILL to
     * programs that didn't terminate gracefully. */
}

Doing it that way the system can shut down gracefully, terminate all programs running in a clean way and unmount all filesystems before doing the reboot, thereby keeing filesystem and data integrity. 这样做可以使系统正常关闭,以干净的方式终止所有运行的程序,并在重新启动之前卸载所有文件系统,从而保证文件系统和数据的完整性。

Note that if you expect your program not to have root access, then you'll have to jump some hoops; 请注意,如果您希望您的程序不具有root访问权限,那么您将不得不跳过一些箍; on systems with systemd you can send a reboot request by D-Bus. 在具有systemd的系统上,您可以通过D-Bus发送重启请求。 But except it to fail, if the user executing the command does not have reboot privileges. 但是除了失败之外,如果执行命令的用户没有重启权限。

I think the important thing here is that the reboot never returns, so your program never really exits normally. 我认为重要的是重启永远不会返回,所以你的程序永远不会真正退出。

In normal conditions (ie a program that exits or even crashes after calling fclose), the file descriptors underlying your FILE * will get closed and their kernel buffers flushed. 在正常情况下(即在调用fclose之后退出甚至崩溃的程序),FILE *下面的文件描述符将被关闭并且其内核缓冲区将被刷新。

In this case, however, since reboot never returns, I suspect that the kernel buffers aren't getting cleaned up in the usual way, and therefore stuff isn't getting written to disk because of it. 但是,在这种情况下,由于重启永远不会返回,我怀疑内核缓冲区没有以通常的方式进行清理,因此没有因为它而写入磁盘。

An fsync call will probably take care of it. fsync调用可能会处理它。 If you want to be paranoid, do fsync, then use fileno() to get a file descriptor and use sync() to insure that the buffers are flushed. 如果你想成为偏执狂,请执行fsync,然后使用fileno()获取文件描述符并使用sync()来确保刷新缓冲区。 At that point there shouldn't be anything of the file left in the process address space, and your call to reboot shouldn't cause any more problems. 此时,进程地址空间中不应该留下任何文件,并且您对重新启动的调用不应该导致更多问题。

An alternate solution is to call sync as per the reboot man page 另一种解决方案是根据重启手册页调用同步

LINUX_REBOOT_CMD_POWER_OFF (RB_POWER_OFF, 0x4321fedc; since Linux 2.1.30). LINUX_REBOOT_CMD_POWER_OFF(RB_POWER_OFF,0x4321fedc;自Linux 2.1.30起)。 The message "Power down." 消息“断电”。 is printed, the system is stopped, and all power is removed from the system, if possible. 如果可能,打印,系统停止,并从系统中移除所有电源。 If not preceded by a sync(2), data will be lost. 如果没有同步(2),则数据将丢失。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM