03-close,write,read ,0/1/2
close,write,read ,0/1/2 这三个文件描述符
close:关闭打开的文件 close(fd)
就算不主动调用close函数关闭打开的文件,进程结束时,也会自动关闭进程所打开的所有文件
linux c库的标准io函数flclose向下调用时,调用就是close系统函数
close关闭文件时做了什么:
1.open打开文件时,会在进程的task_struct结构体中创建相应的结构体,以存放打开文件的相关信息
2.结构体的空间开辟在哪
open函数会通过调用类似malloc的函数,在内存中开辟相应的结构体空间,
如果文件被关闭,存放该文件的被打开的结构体空间就必须释放,类似free(空间地址)
不过malloc和free是给c应用程序调用的库函数,linux系统内部开辟和释放空间时用的是自己特有函数
如果不释放,当前进程的内存空间,会被一堆垃圾信息所占用,随后导致进程奔溃,甚至系统崩溃
因此close文件时,会做一件非常重要的事,释放存放文件打开信息的结构体空间
3.有关task_struct结构体
1).这个结构体用于存放进程在运行过程中,所涉及大的各种信息,其中就包括进程所打开文件的相关信息
2).task_struct结构体,什么时候开辟
进程开始运行时,有linux系统调用自己的系统函数,在内存中开辟的
代码定义的各种变量,开辟这些空间时,这些空间都是来自内存
每个进程都有一个自己的task_struct结构体,用于记录自己的所有相关信息,task_struct记录的就是,进程存活时的一切档案信息
3)什么时候释放
进程结束了,linux系统会调用自己的系统函数,释放这个结构体空间,如果不释放的话,每个进程都开辟一个,进程结束后不释放,会导致系统自己的内存不足,系统崩溃
wrie函数
man 2 write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向fd所指向的文件写入数据
参数:
fd:指向打开的文件
buf:保存数据的缓存空间的其实地址
count:从其实地址开始算起,把缓存中count个字符写入fd所指向的文件
数据中转的过程:应用缓存(buf)->open打开文件时开辟的内核缓存->驱动程序的缓存-》块设备上的文件
返回值:
成功:返回所写的字符个数
失败:返回-1,并给errno自动设置错误号
char buf[]="hello world";
int n=0;
n=write(fd,buf,strlen(buf));
1.n=write(fd,buf+1,strlen(buf)-1);
2.n=write(fd,"hello world",strlen("hello world"));
为何直接写字符串也可以
char buf[]="hello world";
int n=0;
n=write(fd,buf,strlen(buf));
字符直接缓存在了应用空间Buf中,buf代表数组第一个单元h所在的空间地址
直接写字符串常量时,字符串常量被保存(缓存)在了常量区
编译器在翻译
write(fd,"hello world",strlen("hello world"))
时,会直接将hello world翻译为 "hello world"被存放空间的首地址
直接使用字符串常量时,字符串常量代表其实地址
strlen("hello world") 其实就是把起始地址传给了strlen函数
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(){ int fd=0; fd=open("./r.txt",O_RDWR|O_CREAT|O_TRUNC,0664); if(fd ==-1){ perror("open error:"); exit; } char buf[]="hello world"; int n=0; int offset=2; //n=write(fd,buf,strlen(buf)); //ok //n=write(fd,buf+offset,strlen(buf)-offset); //ok n=write(fd,"hello world"+offset,strlen("hello world")-offset); if(n== strlen("hello world")-offset){ printf("write ok\n"); }else{ printf("write err:n=%d\n",n); perror("write err:"); } return 0; }
read函数
man 2 read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
(1)功能:从fd所指向的文件中,将数据读到应用缓存buf中
(2)参数:
fd:指向所打开的文件
buf:读取到数据后用于存放数据的应用缓存的首地址
count:缓存大小(字节数)
(3)返回值
成功:返回读取到的字符个数
失败:返回-1,并给errno自动设置错误号
数据中转过程:块设备上的文件->驱动程序的缓存->open打开文件时开辟的内核缓存->应用缓存(buf)
read(fd,buf+3,11);
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> int main(){ int fd=0; fd=open("./r.txt",O_RDWR,0664); if(fd ==-1){ perror("open error:"); exit; } char buf[30]={0}; read(fd,buf+3,11); int i=0; for(i=3;i<30;i++){ printf("%c",buf[i] ); } printf("-------------------\n"); printf("buf=%s\n",buf ); //直接打印遇到\0即结束 所以为空 }
0/1/2 这三个文件描述符
1.在程序开始运行时,有三个文件自动打开了,打开时分别使用了这三个文件描述符
2.依次打开的三个文件分别是
/dev/stdin,dev/stdout,dev/stderr
[root@centos1 file]# ll /dev/std* lrwxrwxrwx 1 root root 15 7月 31 22:08 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx 1 root root 15 7月 31 22:08 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx 1 root root 15 7月 31 22:08 /dev/stdout -> /proc/self/fd/1
(1)dev/stdin:标准输入文件
a.程序开始运行时,默认调用open("/dev/stdin",O_RDONLY)将其打开,返回的文件描述符是0
b.使用0这个文件描述符,可以获取从键盘输入的数据
简单理解就是:/dev/stdin这个文件代表了键盘
c.read(0,buf,sizeof(buf))
实现从键盘读取数据到缓存buf中
数据中转过程: 键盘->键盘驱动程序的缓存->open /dev/stdin时开辟的内核缓存->read应用缓存到buf
linux下一切皆文件
d. 程序中默认就能使用scanf从键盘输入数据
我们默认就打开了代表键盘的/dev/stdin,打开后0指向这个打开的文件
scanf下层调用的就是read,read自动使用0来读数据,自然就可以从键盘读到数据
scanf("%s",sbuf)--->read(0,rbuf,***)
我们从键盘读取数据时,可以直接调用read这个系统函数来实现,也可以调用scanf c库函数来实现
一般情况下,在实际的应用程序中,更多的还是调用scanf库函数,可以让程序兼容不同OS
scanf在read基础上,加入了更人性化的功能,如格式转换
e.直接使用read的缺点
所有从键盘读取的输入都是字符,从键盘输入123,其实输入的是三个字符'1','2','3',因此使用read函数从键盘读取数据时,读到的永远都是字符
想得到123整型,必须自己将字符处理成整型
如果输入的是浮点数,转起来更麻烦
字符串形式的"123.45"转为真正的浮点数
f.scanf的优点:
可解决read的缺点,给scanf指定%d,%f等格式,scanf会自动将read读到的字符串形式的数据,转化为整型或者浮点型数据
scanf("%d",&a);
在驱动相关的程序里,有些情况只能使用read,不能使用read,read还是有存在意义的
g.fd关闭了,scanf不能正常工作了
close(0);
read(0,buf,len)也不能工作
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> int main(void){ int ret=0; char buf[30]={0}; //ret = read(0,(void *)buf,30); close(0); ret=scanf("%s",buf); if(-1 == ret){ //scanf 用EOF来判断 perror("read err"); exit(1); } printf("buf=%s\n",buf); int num=0; num=(buf[0]-'0')*100+(buf[1]-'0')*10+(buf[2]-'0'); printf("num=%d\n",num); return 0; }
(2) /dev/stdout
a.程序开始运行时,默认open("/dev/stdout",O_WRONLY)将其打开,返回的文件描述符是1
b.通过1这个文件描述符,可以将数据写(打印)到屏幕上显示,/dev/stdout 代表了显示屏
c.write(1,buff,strlen(buf))
write应用缓存->open /dev/stdout时开辟的内核缓存->显示器驱动程序的缓存->屏幕
d.程序中,默认可使用printf打印数据到浏览器
程序在开始运行时,就默认打开代表显示器的/dev/stdout文件,然后1 执向这个代开的文件
printf下层调用的是write函数,write会使用1来写数据,即可将数据写到显示器了
e.使用write函数将65输出到显示器
直接输出
int a=65;
write(1,&a,sizeof(65));
输出的是A,人只看得懂字符,所以所有的输出到屏幕显示的都必须转换成字符
显示时会自动将文字编码翻译为字符图形
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(void){ printf("'0'=%d\n",'0'); char buf[30]={0}; int a=65; write(1,&a,sizeof(65)); write(1,"\n",1); buf[0]=a/10+'0'; buf[1]=a%10+'0'; write(1,buf,strlen(buf)); write(1,"\n",1); return 0; }
printf是对write的封装,兼容不同的os
f.close(1)后printf,write(1)不可以再使用了
(3) /dev/stderr
a.默认open("/dev/stderr",O_WRONLY),返回文件描述符是2
b.通过2这个描述符,可以将报错信息写到屏幕上显示
c.write(2,buf,sizeof(buf));
数据中转:write应用缓存buf->open /dev/stderr时开辟的内核缓存->显示器驱动程序的缓存——>显示器
d. 1和2描述符的区别
均可把文字打印到屏幕
1.普通信息
2.报错信息
printf输出普通信息 调用的是write(1)
perror专门用于输出报错信息,使用的是write(2)
(4)STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
为了使用方便,使用上面的宏代替0,1,2
包含在open或者read或者write函数所需的头文件中

浙公网安备 33010602011771号