文件描述符0,1,2+lseek()+共享文件覆盖解决

1.文件描述符0,1,2

  • 程序开始运行时,有三个文件被自动打开,打开时分别使用了这三个文件描述符
  • 依次打开的三个文件分别是 /dev/stdin,/dev/stdout,/dev/stderr

/dev/stdin 标准输入文件
  1. 程序开始运行时,默认会调用open(“/dev/stdin”, O_RDONLY)打开该文件,返回文件描述符是0

  2. /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;}

运行结果—自己输入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qXfNLC5r-1666777566320)(/home/guojiawei/.config/Typora/typora-user-images/image-20221024172559259.png)]

在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:标准输出文件
  1. 程序开始运行时,默认open(“/dev/stdout”, O_WRONLY)将其打开,返回的文件描述符是1
  2. 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;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0FEYXlX-1666777566322)(/home/guojiawei/.config/Typora/typora-user-images/image-20221024174949480.png)]

因为程序开始运行时,就默认打开了代表了显示器/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标志,写操作时,使用文件长度去更新文件位移量,保证各自操作时,都在文件的尾部操作,就不会出现相互覆盖的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cJQIjuD-1666777566323)(/home/guojiawei/.config/Typora/typora-user-images/image-20221026174521450.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值