一文件相关的系统调用
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);
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可以获取)
说了这么多次文件描述符,那么文件描述符到底是什么呢
files_struct结构体里面的内容:
files_struct结构保存了进程打开的所有文件表数据,描述一个正被打开的文件。Linux中一个进程最多只能同时打开NR_OPEN_DEFAULT个文件,而且前三项分别设为标准输入、标准输出和出错信息输出文件,定义如下:
struct files_struct {
atomic_t count; //自动增量
struct fdtable *fdt;
struct fdtable fdtab;
fd_set close_on_exec_init; //执行exec时 需要关闭的文件描述符初值集合
fd_set open_fds_init; //当前打开文件 的文件描述符屏蔽字
struct file * fd_array[NR_OPEN_DEFAULT];
spinlock_t file_lock; /* Protects concurrent writers. Nests inside tsk->alloc_lock */
};
file结构体:
文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。在内核创建和驱动源码中,struct file的指针通常被命名为file或filp
二、重定向
看下面代码:
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的文件中、
//那么这里就不会看到标准输出上与内容输出
//而是在我们打开的文件中
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);
//修改文件句柄,完成重定向
//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.无缓冲(系统调用,write)
2.行缓冲 (显示器)按行刷新换缓冲区
3.全缓冲(文件)缓冲区满了才会进行刷新
当然每一个进程结束会刷新缓冲区
当我们在标准输出上进行输出时是按行刷新自己的缓冲区的,其实是没有什么不同的,因为这里的字符串后面都加了'\n',会自己刷新缓冲区,创建一个子进程后,子进程的缓冲区中为空
因为输出到一个文件中时是一种全缓冲(等到缓冲区满了才进行刷新)
所以当将其输出重定向到一个文件中的话,是一个全缓冲,父进程将字符串输出后,并没有进行刷新,字符串仍然在父进程的缓冲区中,创建一个子进程的话,子进程会将父进程发数据进行拷贝,那么子进程的缓冲区中就会有和父进程同样的字符串,结束时刷新缓冲区,字符串就会被刷新到目标文件,所以会出现两遍printf()和fprintf()的内容打印了两遍。write()是属于系统调用,没有缓冲区,直接输出,所以是一遍。
上面两个printf()函数和fprintf()函数底层都是调用系统调用write(),但是系统调用在用户态是没有缓冲区的,所以上面说的缓冲区缓冲区是C库提供的