Linux下中断程序导致写文件失败的分析
案例:
一个普通linux C程序,执行期间会进行多次printf操作,利用bash脚本重定向功能,将stdout重定向到一个另一个文件中去。在运行途中用ctrl+C终止程序,发现定向文件始终为空,即写失败。
分析:
原本以为是bash重定向机制导致的问题,于是将重定向取消,改为使用fprintf,而非printf。即在C程序内部进行写文件。发现问题依旧。(排除fopen打开失败的因素)
仔细观察,发现问题集中在两个层面,一个是ctrl+c到底做了什么,二是写文件操作为什么失败。
首先,ctrl+C不代表杀死进程,仅仅代表中断。中断和杀死并不是完全一样的概念。google了一下,有以下发现:
“Linux中的kill命令用来终止指定的进程(terminate a process)的运行,是Linux下进程管理的常用命令。通常,终止一个前台进程可以使用Ctrl+C键,但是,对于一个后台进程就须用kill命令来终止,我们就需要先使用ps/pidof/pstree/top等工具获取进程PID,然后使用kill命令来杀掉该进程。kill命令是通过向进程发送指定的信号来结束相应进程的。在默认情况下,采用编号为15的TERM信号。TERM信号将终止所有不能捕获该信号的进程。对于那些可以捕获该TERM信号的进程就要用编号为9的kill信号,强行“杀掉”该进程。”
“
1、kill命令可以带信号号码选项,也可以不带。如果没有信号号码,kill命令就会发出终止信号(15),这个信号可以被进程捕获,使得进程在退出之前可以清理并释放资源。也可以用kill向进程发送特定的信号。例如:
kill -2 123
它的效果等同于在前台运行PID为123的进程时按下Ctrl+C键。但是,普通用户只能使用不带signal参数的kill命令或最多使用-9信号。
2、kill可以带有进程ID号作为参数。当用kill向这些进程发送信号时,必须是这些进程的主人。如果试图撤销一个没有权限撤销的进程或撤销一个不存在的进程,就会得到一个错误信息。
3、可以向多个进程发信号或终止它们。
4、当kill成功地发送了信号后,shell会在屏幕上显示出进程的终止信息。有时这个信息不会马上显示,只有当按下Enter键使shell的命令提示符再次出现时,才会显示出来。
5、应注意,信号使进程强行终止,这常会带来一些副作用,如数据丢失或者终端无法恢复到正常状态。发送信号时必须小心,只有在万不得已时,才用kill信号(9),因为进程不能首先捕获它。要撤销所有的后台作业,可以输入kill 0。因为有些在后台运行的命令会启动多个进程,跟踪并找到所有要杀掉的进程的PID是件很麻烦的事。这时,使用kill 0来终止所有由当前shell启动的进程,是个有效的方法。
6、只有第9种信号(SIGKILL)才可以无条件终止进程,其他信号进程都有权利忽略。 下面是常用的信号:
HUP 1 终端断线
INT 2 中断(同 Ctrl + C)
QUIT 3 退出(同 Ctrl + \)
TERM 15 终止
KILL 9 强制终止
CONT 18 继续(与STOP相反, fg/bg命令)
STOP 19 暂停(同 Ctrl + Z)
”
至此,可以得出一个结论:我们平常所按ctrl+C不等价于“终止进程”。ctrl+C一般情况下等价于kill -s SIGINT。即进程接受的是SIGINT信号。而接受了SIGINT信号并不是简单的杀死进程。
在一般程序编写中,可以利用signal.h中所包含的相关函数(如sigaction)来对这些不同类别的信号进行响应,从而进行对应的后处理,如资源的释放等等。
如此,在bash脚本中如若出现:
$cmd &
spid=$!
kill -s SIGINT $spid
是无法杀死进程的。进程仍旧在后台运行。若改成kill -9 $spid。便可杀死进程。
扯的有点远了。回到之前的问题,无论采取上述哪种kill方式,都会导致写文件失败。
那面对这些kill,平常的程序到底如何应对写文件之类的操作呢?再google一下,得到以下有用信息:
“SIGINT can be caught and handled. It's like telling the process "Please stop what you're doing." The process is free to ignore the signal, or implement a handler that does anything it wants. The default behavior is to terminate, and this is what most processes will do. It's typical, but not required, for a process to handle SIGINT by gracefully terminating -- closing any open files, network connections, or database handles and stopping the current operation in such a way that prevents data loss or corruption.
SIGKILL can't be handled by the receiving process. If a process gets sent SIGKILL, it's toast, period. If a process receives SIGKILL in the middle of a database transaction or file write (for instance), it has no chance to exit gracefully and data loss or corruption may occur.
When you do Ctrl-C in the terminal, the terminal sends SIGINT to the running process. Like I said before, most processes will gracefully terminate on receiving SIGINT. Once it's terminated, it's just as surely ended as if it had been SIGKILL'ed or exited normally -- you shouldn't expect to see it in ps because it's still gone.
But there are some programs that don't respond to SIGINT in that way. bash is one example; if you hit Ctrl-C at a shell prompt, it'll just cancel whatever you've typed on that line -- not terminate the whole shell. Again, this is because the behavior when a process receives SIGINT is determined by the process itself. vi is another program that doesn't handle SIGINT by terminating.”
和我们上面分析的一样,大部分的程序都需要一个handler来应对SIGINT信号。文中提到了exit normally。看来只有正常退出才能保证写文件成功?
stackoverflow上面有一个问答正好解决了我的疑问。
Q:
I've read in a man page that when exit() is called all streams are flushed and closed automatically. At first I was skeptical as to how this was done and whether it is truly reliable but seeing as I can't find out any more I'm going to accept that it just works — we'll see if anything blows up. Anyway, if this stream closing behavior is present in exit() is such behavior also present in the default handler for SIGINT (the interrupt signal usually triggered with Ctrl+C)? Or, would it be necessary to do something like this:
#include <signal.h>
#include <stdlib.h>
void onInterrupt(int dummy) { exit(0); }
int main() {
signal(SIGINT, onInterrupt);
FILE *file = fopen("file", "a");
for (;;) { fprintf(file, "bleh"); } }
to get file to be closed properly? Or can the signal(SIG... and void onInterrupt(... lines be safely omitted?
Please restrict any replies to C, C99, and POSIX as I'm not using GNU libc. Thanks.
A:
1.So in C99, if it's closed then it's flushed.
2.You'll have to handle the signal if you want your buffers flushed. Otherwise the process will be terminated and the file descriptors closed without flushing the stdio buffers.
By default, a SIGINT will terminate the process abnornally. Processes so terminated do not call exit() and so do not have buffers flushed.
也就是说,只有正常退出,才能做到flush。否则将写失败。
之后有百度了下中文资料,发现同样的结论。
“fflush库函数的作用是把文件流里的所有未写出数据立刻写出。例如,你可以用这个函数来确保在试图读入一个用户响应之前,先向终端送出一个交互提示符。使用这个函数还可以确保在程序继续执行之前重要的数据都已经被写到磁盘上。有时在调试程序时,还可以用它来确定程序是正在写数据而不是被挂起了。注意,调用fclose函数隐含执行了一次flush操作,所以不必在fclose之前调用fflush。
fclose库函数关闭指定的文件流stream,使所有尚未写出的数据都写出。因为stdio库会对数据进行缓冲,所以使用fclose是很重要的。如果程序需要确保数据已经全部写出,就应该调用fclose函数。虽然当程序正常结束时,会自动对所有还打开的文件流调用fclose函数,但这样做就没有机会检查由fclose报告的错误了。与文件描述符一样,可用文件流的数目也是有限制的。这个限制由头文件stdio.h中的FOPEN_MAX常量定义,最小为8。”
“所谓flush一个缓冲,是指对写缓冲而言,将缓冲内的数据全部写入实际的文件,并将缓冲清空,这样可以保证文件处于最新的状态。之所以需要flush,是因为写缓冲使得文件处于一种不同步的状态,逻辑上一些数据已经写入了文件,但实际上这些数据仍然在缓冲中,如果此时程序意外地退出(发生异常或断电等),那么缓冲里的数据将没有机会写入文件。flush可以在一定程度上避免这样的情况发生。”
所以说,平时咱写程序,需要谨慎和按流程来,fclose做的事情有很多,不要全指望main函数return后自动帮你close文件。因为一旦出现上述中断的情形,就会生问题。
解决方案:
分析了这么多,解决方案相比也很明了了。
方案一:
在C程序中加入SIGINT响应函数,保证程序正常退出。
方案二:
在C程序中加入fflush函数,保证所有输出第一时间写入文件。
方案一才是最好的解决方案,而方案二有些hack了。
就这样。
Linux下中断程序导致写文件失败的分析的更多相关文章
- Linux下修改默认字符集--->解决Linux下Java程序种中文文件夹file.isDirectory()判断失败的问题
一.问题描述: 一个项目中为了生成树状目录,调用了file.listFiles()方法,然后利用file.isDirectory()方法判断是否为目录,该程序在windows下运行无问题,在Linux ...
- Linux下putenv()函数导致composer更新失败
bug复现: 原因: putenv() 函数设置特定的环境变量有可能是一个潜在的安全漏洞,所以这个函数在php配置文件中是默认禁止的,在 php.ini 中查找此函数,然后将此函数删除掉,重载配置即可 ...
- Windows和Linux下putenv()函数导致composer更新失败
bug复现: 原因: putenv() 函数设置特定的环境变量有可能是一个潜在的安全漏洞,所以这个函数在php配置文件中是默认禁止的,在 php.ini 中查找此函数,然后将此函数删除掉,重载配置即可 ...
- 位图文件(BMP)格式以及Linux下C程序实现(转)
源:位图文件(BMP)格式以及Linux下C程序实现 说到图片,位图(Bitmap)当然是最简单的,它是Windows显示图片的基本格式,其文件扩展名为*.BMP.由于没有经过任何的压缩,故BMP图 ...
- Linux下C程序的编辑,编译和运行以及调试
国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html 内部邀请码:C8E245J (不写邀请码,没有现金送) 国 ...
- Linux下的文件结构,及对应文件夹的作用
Linux下的文件结构,及对应文件夹的作用 /bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基点,比 ...
- linux下c程序的链接、装载和库(1)
读完<程序员的自我修养--链接.装载和库>相关章节,想来总结一下,若有错误,请指正,多谢. 1. 什么叫目标文件? 你的工程里有很多xxx.c这样的源文件,这些文件是文本文件,只有人能够认 ...
- linux 下C语言编程库文件处理与Makefile编写
做开发快3年了,在linux下编译安装软件算是家常便饭了.就拿gcc来说,都有不下10次了,可基本每次都会碰到些奇奇怪怪的问题.看来还是像vs.codeblocks这样的ide把人弄蠢了.便下定决心一 ...
- Linux下C程序的内存映像
2.Linux下C程序的内存映像 2.1. 代码段.只读数据段(1)对应着程序中的代码(函数),代码段在Linux中又叫文本段(.text)(2)只读数据段就是在程序运行期间只能读不能写的数据,con ...
随机推荐
- centos6.5 phpmyadmin 您应升级到 MySQL 5.5.0 或更高版本
看到自己当初写的,并没有直接的解决问题,而是退而求其次,安装低版本的mysql5.1,然后安装对应版本的phpmyadmin 4.0.10.5 UnicodeDecodeError: 'ascii' ...
- 如何学习web开发环境搭建和脚手架
Web前端的学习路线 第一阶段: HTML+CSS: HTML进阶.CSS进阶.div+css布局.HTML+css整站开发. JavaScript基础: Js基础教程.js内置对象常用方法.常见DO ...
- 哈希+Bfs【P2730】 魔板 Magic Squares
没看过题的童鞋请去看一下题-->P2730 魔板 Magic Squares 不了解康托展开的请来这里-->我这里 至于这题为什么可以用康托展开?(瞎说时间到. 因为只有8个数字,且只有1 ...
- CentOS 7安装tensorflow 1.6
pip3 install tensorflow-gpu==1.6 1.6 依赖于cuda 9.0 cuDNN 7,并且前提需要下载和安装nvidia驱动. 安装驱动 nvidia 驱动地址: http ...
- AOJ 2251 Merry Christmas (最小点覆盖)
[题目链接] http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2251 [题目大意] 给出一张图,现在有一些任务,要求在ti时刻送礼物 ...
- 【莫队算法】【权值分块】poj2104 K-th Number / poj2761 Feed the dogs
先用莫队算法保证在询问之间转移的复杂度,每次转移都需要进行O(sqrt(m))次插入和删除,权值分块的插入/删除是O(1)的. 然后询问的时候用权值分块查询区间k小值,每次是O(sqrt(n))的. ...
- python3开发进阶-Django视图(View)的常见用法
阅读目录 简述Django的View(视图) CBV和FBV Request对象和Response对象 Django组件(render,redirect)详解 一.简述Django的View(视图) ...
- 5.7(java学习笔记)Vector、Enumeration
一.Vector Vector类实现一个可扩展的数组对象.与数组一样,它包含可以使用整数索引访问. 它的基本操作方法add(int index, E element),get(int index),i ...
- [JQuery]用InsertAfter实现图片走马灯展示效果2——js代码重构
写在前面 前面写过一篇文章<[JQuery]用InsertAfter实现图片走马灯展示效果>,自从写过那样的也算是使用面向对象的写法吧,代码实在丑陋,自从写过那样的代码,就是自己的一块心病 ...
- 【java】处理时间字段 在数据库查询的时候只想要年月日,不想要时分秒 ,java中设置时间类型为年月日,java中设置Date中的时分秒为00.00.000
JDK8 中最简单的处理方式: @Test public void dateTest(){ Date now = new Date(); System.out.println(now); // jav ...