一文件相关的系统调用
C语言标准库函数中有对文件的操作
fopen(),fwrite(),fread(),fclose();等
FILE *fd=fopen("./test.txt","w"); if(fd==NULL) { perror("fopen"); return 1; } char * str=(char *)"hello world"; fwrite(str,strlen(str),1,fd); fclose(fd);
操作系统中对文件的操作有系统调用
open(),write(),read(),cloes()等
事实上C语言中的文件操作函数是通过系统调用来实现的。
当我们打开一个文件时,系统会为该文件分配一个文件描述符,这个文件描述符是一个比较小得整数
当操作系统创建一个进程时,会先对这个进程先进行描述,在进行组织
对进程进行描述是用一个结构体,称为进程控制块(PCB),对进程进行组织是用一个链表将这些进程控制块链在一起
一个进程中可以打开多个文件,同样,操作系统对文件也是先进行描述,再进行组织
文件描述也是用一个结构体来,对其进行组织也是用一个链表将其链上去
下面是一个简单的例子
int fd=open("test.txt",O_WRONLY | O_CREAT,0644); if(fd<0) { perror("open");exit(1); } char * str=(char *)"hello world"; printf("fd:%d\n",fd); write(fd,str,strlen(str)); close(fd);
这里的文件描述符打印出来是3
这是因为操作系统会为进程默认打开三个文件,分配三个文件描述符0,1,2,分别是标准输入,标准输出,标准错误。
文件描述符默认从空闲的文件描述符中最小的开始分配
close(0); close(2); int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644); int fd_2=open("test2.txt",O_WRONLY|O_CREAT,0644); if(fd_1<0||fd_2<0) { perror("open");exit(1); } //这里默认从文件描述符中找最小的 printf("fd_1:%d\n",fd_1); printf("fd_2:%d\n",fd_2);
操作系统中关于文件的相关系统调用
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
这里注意read()函数
返回值sszie_t 是一个有符号长整型,返回值大于0时,表示本次实际读的字符个数,小于0时表示读取失败,等于0时表示读到文件的结束标志EOF(在Linux下是Ctrl+D,windows下是Ctrl+Z)
说了这么多次文件描述符,那么文件描述符到底是什么呢
二、重定向
看下面代码:
close(1); int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644); if(fd_1<0) { perror("open");exit(1); } //这里默认从文件描述符中找最小的,所以这里分配的文件描述符为1 printf("fd_1:%d\n",fd_1); //因为printf()函数底层实现还是调用了系统调用fprintf(); //fprintf(stdout,"%d\n",fd_1) //所以这里其实是将内容输出重定向到文件描述符为1的文件中、 //那么这里就不会看到标准输出上与内容输出 //而是在我们打开的文件中
//修改文件句柄,完成重定向 //dup2(int oldfd,int newfd);这个函数的作用是用newfd是拷贝覆盖了oldfd //相当于对newfd文件的操作其实就是oldfd的操作。 int fd=open("test.txt",O_CREAT | O_WRONLY,0644); if(fd<0) { perror("open");exit(1); } dup2(fd,1); printf("fd : %d\n",fd);//就是相当于对文件描述符为1中的问价写其实就是对文件描述符为fd的文件进行操作 const char * str="nihao shijie\n"; write(1,str,strlen(str)); close(fd);
//三种输出“hello world”
//多种打印hello world const char * str="hello world\n"; printf("%s",str); write(1,str,strlen(str)); fprintf(stdout,"%s",str);
当我们在函数结束的时候创建一个子进程呢?
const char * str_printf="str_printf\n"; const char * str_fprintf="str_fprintf\n"; const char * str_write="str_write\n"; printf("%s",str_printf); fprintf(stdout,"%s",str_fprintf); write(1,str_write,strlen(str_write)); fork();
缓冲区刷新格式:
1.无缓冲
2.行缓冲 (显示器)按行刷新换缓冲区
3.全缓冲(文件)缓冲区满了才会进行刷新
当然每一个进程结束会刷新缓冲区
当我们在标准输出上进行输出时是按行刷新自己的缓冲区的,其实是没有什么不同的,因为这里的字符串后面都加了'\n',会自己刷新缓冲区,创建一个子进程后,子进程的缓冲区中为空
因为
输出到一个文件中时是一种全缓冲(等到缓冲区满了才进行刷新)
所以当将其输出重定向到一个文件中的话,是一个全缓冲,父进程将字符串输出后,并没有进行刷新,字符串仍然是父进程的数据,创建一个子进程的话,子进程的缓冲区中就会有字符串,结束时刷新缓冲区,字符串就会被刷新到目标文件,所以会出现两个
上面两个printf()函数和fprintf()函数第层都是调用系统调用write(),但是系统调用却没有缓冲区,所以上面说的缓冲区缓冲区是C库提供的
但是这里的write()是属于系统调用,没有缓冲区,直接输出,没有影响。