1.文件描述符0,1,2
- 程序开始运行时,有
三个文件被自动打开
,打开时分别使用了这三个文件描述符 - 依次打开的三个文件分别是 /dev/stdin,/dev/stdout,/dev/stderr
/dev/stdin 标准输入文件
程序开始运行时,默认会调用open(“/dev/stdin”, O_RDONLY)打开该文件,返回文件描述符是0
/dev/stdin这个文件代表了键盘-----使用0这个文件描述符就可以从键盘输入数据
#include <unistd.h> #include<stdio.h> int main(){char buf[30]={0};read(0,buf,30);printf("buf=%s\n",buf);return 0;}
运行结果—自己输入
在Linux下,应用程序通过OS API以文件形式操作底层硬件
不管是读键盘,还是向显示器输出文字显示,都是以文件形式来读写的
在Linux下一切皆文件,说的就是这么个意思
scanf下层调用的就是read,read自动使用0来读数据,自然就可以从键盘读到数据。
scanf(“%s”, sbuf) C库函数
|
|
read(0, rbuf, ***)
在scanf函数之前,close(0),会发生错误-------直接不让你输入,scanf底层实现需要调用read
int main(){char buf[30]={0};close(0);scanf("%s",buf); printf("buf=%s\n",buf);return 0;
}
/dev/stdout:标准输出文件
- 程序开始运行时,默认open(“/dev/stdout”, O_WRONLY)将其打开,返回的文件描述符是1
- dev/stdout这个文件代表了显示器—通过
1这个文件描述符
,可以将数据写(打印)到屏幕上显示
write(1, buf, strlen(buf))
buf数据显示到屏幕上的,数据中转过程:
write应用缓存buf ——> open /dev/stdout时开辟的内核缓存——> 显示器驱动程序的缓存——> 键盘
#include <unistd.h> #include<stdio.h> int main(){write(1,"hello,world\n",12);return 0; }
因为程序开始运行时,就默认打开了代表了显示器/dev/stdout文件,然后1指向这个打开的文件。
printf下层调用的是write函数,write会使用1来写数据,
既然1所指文件代表的是显示器,自然就可以将数据写到显示器了
int a=100;write(1,&a,sizeof(a));//注意是取地址
//输出是ASCII编码
/*下面是转换成数字*/
int main(){int a=72;write(1,&a,sizeof(a));//注意是取地址char buf[2]={0};buf[0]=a/10+'0';buf[1]=a%10+'0';write(1,buf,2);return 0;
}
//也就是printf通过指定%d、%f等,自动将其换为对应的字符,然后write输出,完全不用自己来转换。
/dev/stderr:标准出错输出文件
默认open(“/dev/stderr”, O_WRONLY)将其打开,返回的文件描述符是2
通过2这个文件描述符,可以将报错信息写(打印)到屏幕上显示
int main(){int a=72;write(2,&a,sizeof(a));//注意是取地址write(2,"hello",5);return 0;
}
1和2啥区别?
使用这两个文件描述符,都能够把文字信息打印到屏幕。如果仅仅是站在打印显示的角度,其实用哪一个文件描述符都能办到。
1、2:有各自的用途
1:专用于打印普通信息
2:专门用于打印各种报错信息使用write输出时:
- 如果你输出的是普通信息,就使用1输出。
- 如果你输出的是报错信息,就使用2输出
int main(){perror("fail");return 0; }
执行成功,但是:
int main(){close(2);perror("fail");return 0; }
会出错,close(1)不出错
2.lseek函数
lseek函数是设置偏移量的值,也就是改变文件指针的位置,从而实现对文件的随机存取。
lseek所需的头文件和函数原型
#include <sys/types.h>
#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);
- offset----偏移量,以字节为单位,可正可负
- whence----粗定位,有三个符号常量
- SEEK_SET-----距离文件起始位置
- SEEK_CUR-----文件当前位置
- SEEK_END----距离文件结束位置
不过当whence被指定为SEEK_SET时,如果offset被指定为负数的话,是没有意义:
因为已经到文件头上了,在向前移动就越界了,不再当前文件的范围内了
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
int main(){int fd=open("mm.txt",O_RDWR|O_CREAT|O_EXCL,0666);//在文件从‘a’写到‘z’for(char i='a';i<='z';i++)write(fd,&i,sizeof(char));lseek(fd,3,SEEK_SET);//偏移文件头char c;read(fd,&c,1);printf("%c",c);close(fd);return 0;
}
返回值
- 调用成功-------返回当前读写位置相对于文件开始位置的偏移量(字节)
- 调用失败-------返回-1,并给errno设置错误号
od -c 文件:以字符形式查看文件内容。
3.文件描述符表
当open打开文件成功后,会创建相应的结构体(数据结构),用于保存被打开文件的相关信息,对文件进行读写等操作时,会用到这些信息,
这个数据结构就是“文件描述符表”
进程表:task_struct
- 每一个进程运行起来后,Linux系统都会为其在内存中开辟一个task_struct结构体
- task_struct专门用于存放进程在运行过程中,所涉及到的所有与进程相关的信息
其中,文件描述符表就被包含在了task_struct中
-
这里文件描述符表的fd值存放打开的文件
-
i节点信息就是文件属性信息(ls -lah来查看)
-
文件长度就是文件大小
文件状态标志
-------就是open文件时指定的O_RDONLY、O_WRONLY、O_RDWR、O_TRUNC、O_APPEND、O_CREAT、O_EXCL、O_NONBLOCK、O_ASYNC等。
-------作用:读写文件时,会先检查“文件状态标志”,看看有没有操作权限
,然后再去操作文件
4.共享操作文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
//把文件名设置成宏
#define FILE_NAME "io.txt"
void print_error(char* str){perror(str);exit(-1);
}
int main(){int fd1=0;int fd2=0;fd1=open(FILE_NAME,O_RDWR);if (fd1==-1) print_error("1 open fail!");fd2=open(FILE_NAME,O_RDWR); if (fd2==-1) print_error("2 open fail!");int i=0;while(i++<100){write(fd2,"world\n",6);sleep(1);//防止写入速度过快write(fd1,"hello\n",6);}return 0;
}
fd1和fd2会发生相互覆盖的情况
printf(“fd1=%d,fd2=%d\n”,fd1,fd2);-----一个是3,一个是4
在同一进程里面,一旦某个文件描述符被用了,在close释放之前,别人不可能使用,所以指向同一文件的描述符不可能相同
覆盖原因:
正是由于不同的文件描述符,各自对应一个独立的文件表,在文件表中有属于自己的“文件位移量”,开始时都是0。各自从0开始写,每写一个字节向后移动一个字节,他们写的位置是重叠的,因此肯定会相互的覆盖。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include<stdio.h> #include<stdlib.h> //把文件名设置成宏 #define FILE_NAME "io.txt" void print_error(char* str){perror(str);exit(-1); } int main(){int fd1=0;int fd2=0;fd1=open(FILE_NAME,O_RDWR|O_APPEND);if (fd1==-1) print_error("1 open fail!");fd2=open(FILE_NAME,O_RDWR|O_APPEND); if (fd2==-1) print_error("2 open fail!");printf("fd1=%d,fd2=%d\n",fd1,fd2);int i=0;while(i++<100){write(fd2,"world\n",6);sleep(1);//防止写入速度过快write(fd1,"hello\n",6);}return 0; }
注意:两个都得指定O_APPEND,只指向其中一个,依然覆盖
多个进程操作同一个文件
不同进程打开同一文件时,各自使用的文件描述符值可能相等,比如我们例子中的1和2进程,它们open后的描述符就相等。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//把文件名设置成宏
#define FILE_NAME "io.txt"
void print_error(char* str){perror(str);exit(-1);
}
int main(){int fd1=0;fd1=open(FILE_NAME,O_RDWR|O_TRUNC);if (fd1==-1) print_error("1 open fail!");printf("fd1=%d\n",fd1);int i=0;while(i++<30){write(fd1,"World\n",6);sleep(1);//防止写入速度过快}return 0;
}
file1.c和file2.c只有write语句不同,一个是hello,一个是world.然后各自执行
同样的,指定O_APPEND标志,写操作时,使用文件长度去更新文件位移量,保证各自操作时,都在文件的尾部操作,就不会出现相互覆盖的情况。