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 ...
随机推荐
- zsh命令行
Linux/Unix提供了很多种Shell,为毛要这么多Shell?难道用来炒着吃么?那我问你,你同类型的衣服怎么有那么多件?花色,质地还不一样.写程序比买衣服复杂多了,而且程序员往往负责把复杂的事情 ...
- Aras增加新用户
Aras中新增用户,这里特别提醒,用户密码下面的可以登陆必须勾选,如不勾选刚出现不能登陆的情况. 增加用户后,将用户加入至与原同事一样的Identities(例如Sales/All Employees ...
- 肖申克的救赎 -Hope
典狱长诺顿高高在上,平时道貌岸然,对圣经倒背如流,实际上攫取利益时不择手段,残酷.阴险而贪婪.狱警长海利和其他警员,凶狠残暴,充当诺顿的打手,草菅囚犯的人命.他们是不是象极了我们现实中的掌权阶层?我称 ...
- POJ 3264.Balanced Lineup-结构体版线段树(区间查询最值)
Balanced Lineup Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 53721 Accepted: 25244 ...
- 洛谷 P1583 魔法照片【二级结构体排序】
题目描述 一共有n(n≤20000)个人(以1--n编号)向佳佳要照片,而佳佳只能把照片给其中的k个人.佳佳按照与他们的关系好坏的程度给每个人赋予了一个初始权值W[i].然后将初始权值从大到小进行排序 ...
- OJP1147括号匹配加强版(栈)与P1153乱头发节(单调栈)
惨兮兮的被刷掉2%的通过率后在经过思考和dalao的指点后终于A掉了这道题 强烈建议修改这题的样例,实在太迷惑人,各种错误算法都能过 比如说这是一份错误代码,看懂了也不要学思路,和正解不知道差到哪里去 ...
- [BZOJ2655]calc(拉格朗日插值法+DP)
2655: calc Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 428 Solved: 246[Submit][Status][Discuss] ...
- 10.3(Java学习笔记)JDBC时间操作
一.时间分类 数据库 java类 Date ---- java.sql.Date 表示日期 yyyy-MM--dd (年月日) Time ----java.sql.Time 表示 ...
- 6.4(java学习笔记)转换流
一.乱码问题 我们来看下列例子: public class ConStream { //当前平台默认采用GBK public static void main(String[] args){ Stri ...
- iOS 调H5方法不执行没反应的坑
调用H5的方法需要给H5传一些参数,参数中包括图片的base64字符串. 错误一: 图片转base64,后面参数不能随便写,正确做法如下 NSData *imageData = UIImageJPEG ...