今天在学习进程时遇到关于一个I/O缓冲区的的问题,和大家分享一下,首先举个简单的例子:
#include <stdio.h> int main() { printf("hello,world!"); _Exit(0); }
编译成功后却不出现hello,world,这是为什么呢,注意,在代码中printf语句打印hello,world的字符串最后面没带换行符.而且最后调用了_Exit函数,这导致了在终端上显不出hello,world. 首先介绍关于标准I/O的几种缓冲机制: 1.全缓冲:全缓冲指的是系统在填满标准的I/O缓冲区后才进行实际的I/O操作,注意,对于驻留在磁盘上的文件来说通常是由标准的I/O库实施全缓冲. 2.行缓冲: 在这种情况下,标准的IO在输入和输出中遇到换行符执行IO操作;注意,当流涉及终端时,都使用行缓冲. 3.无缓冲:无缓冲指的是标准的IO库不对字符进行缓冲存储,注意,标准的出错流stderr通常是无缓冲的. 在看几个退出函数: 1.exit(),调用exit函数之后,它首先执行一系列的清理处理,包括调用执行各种终止处理程序,关闭所有标准IO流等,然后进入内核; 2._exit().与exit不同的是,它不进行清理直接进入内核此函数由POSIX.1说明,放在unistd.h里面. 3._Exit ()。同样,它也不进行清理工作而直接进入内核。此函数跟exit一样由ISO C说明,放在stdlib.h里面。 所以可以有很多方法修正这段代码: 1.在hello,world后加一个换行符,此时行缓冲遇到换行符\n.执行实际IO操作. 2.调用exit()函数,让它帮我们进行相应的IO操作,也就是把_Exit(),换成exit(); 3.改变标准的输出流的默认缓冲区,这个要用到函数setvbuf(),那就先介绍以下这个函数吧: #include <stdio.h> void setbuf(FILE *stream, char *buf); void setbuffer(FILE *stream, char *buf, size_t size); void setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode, size_t size); 函数说明: 1.对于setbuf()函数,buf指出的缓冲区长度由头文件stdio.h中定义的 宏BUFSIZE的值决定,缺省值为512字节。当选定buf为空时,setbuf函数将使的文件I/O不带缓冲。 2.setvbuf函数,则由 malloc函数来分配缓冲区。参数size指明了缓冲区的长度(必须大于0),而参数type则表示了缓冲的类型,其值可以取如下值: 3.在打开文件流后,读取内容之前,调用setbuffer()可用来设置文件流的缓冲区。参数stream为指定的文件流,参数buf指向自定的缓冲区起始地址,参数size为缓冲区大小。 4. setlinebuf()用来设置文件流以换行为依据的缓冲IO,即行缓冲。 type 值 含义 _IOFBF 文件全部缓冲,即缓冲区装满后,才能对文件读写 _IOLBF 文件行缓冲,即缓冲区接收到一个换行符时,才能对文件读写 _IONBF 文件不缓冲,此时忽略buf,size的值,直接读写文件,不再经过文件缓冲区缓冲 所以我们可以通过调用setvbuf函数,把标准的输出流默认的行缓冲变成无缓冲 setvbuf(stdout, NULL, _IONBF, 0); 现在说一说我今天遇到的具体的问题吧,大家都知道,fork一个子进程,子进程会复制父进程的许多资源(这个自己查),最重要的是它会复制父进程的缓冲区, 举个例子吧,
#include <stdio.h> #include <sys/types.h> #include <stdlib.h> #include <unistd.h> extern char **environ; int main() { pid_t pid; int stat_val; char *argv[]={"ls","-al","/",NULL}; //printf("exec函数族实例:\n");//去掉注释; printf("exec函数实例:"); pid=fork(); switch(pid) { case -1: perror("进程创建失败!\n"); exit(1); case 0: printf("子进程正在运行!\n"); printf("我的ID=%d,父亲的ID=%d\n",getpid(),getppid()); //execve("/bin/ls",argv,environ); //execv("/bin/ls",argv); //execvp("ls",argv); //execl("/bin/ls","ls","-al","/",NULL); // execlp("ls","ls","-al","/",NULL); execvp("ls",argv); printf("如果exec函数调用成功,这一句不会被执行!\n"); exit(0); default: printf("父进程正在运行!\n"); break; } wait(&stat_val); return 0; }
编译运行:
yang@liu:~/Linux C$ gcc processimage1.c yang@liu:~/Linux C$ ./a.out exec函数实例:父进程正在运行! exec函数实例:子进程正在运行! 我的ID=6032,父亲的ID=6031 总用量 105 drwxr-xr-x 24 root root 4096 7月 13 12:11 . drwxr-xr-x 24 root root 4096 7月 13 12:11 .. drwxr-xr-x 2 root root 4096 5月 31 11:42 bin
然后把注释写//去掉注释那一行注释去掉,注释掉下面那一行: 编译运行:
exec函数族实例: 父进程正在运行! 子进程正在运行! 我的ID=7354,父亲的ID=7353 总用量 105 drwxr-xr-x 24 root root 4096 7月 13 12:11 . drwxr-xr-x 24 root root 4096 7月 13 12:11 .. drwxr-xr-x 2 root root 4096 5月 31 11:42 bin drwxr-xr-x 4 root root 1024 5月 31 14:20 boot
第一次没有加进行加\n,所以父进程运行后"exec函数族实例:"就存在于父进程的缓冲区中,当fork后,子进程也复制了父进程的缓冲区,所以输出了两次"exec函数族实例", 第二次,加了\n,进行了行缓冲清理,所以只由父进程输出一次;