Linux之基础IO(1)

种一棵树最好的时间是十年前,其次是现在!

1.预备知识

1.在系统角度去理解文件,文件 = 内容+属性(也是数据);
在Linux下,一切皆文件!
键盘,显示器,可以被看出文件吗?
我们从来没有打开过键盘和显示器,但我们依旧可以进行sanf,fgets,printf,cout,…,为什么?
其实,我们的C/C++程序 会默认打开三个文件流
标准输入(键盘) extern FILE* stdio
标准输出(显示器)extern FILE* stdout
标准错误(显示器)extern FILE* stderr

2.文件 = 内容+属性,所以对文件的所有操作,无外乎:a.对内容 b.对属性
文件在磁盘上放着,我们访问文件,先写代码,编译成exe可执行文件,在运行,在访问文件,本质是谁在访问文件呢?进程。
要向硬件写入,只有谁有权利呢?操作系统。
普通用户也想写入呢?必须让OS提供接口,也就是文件类的调用接口!

3.我们进行文件操作的时候,为什么要对文件类的系统接口进行封装呢?
(1).比较难,语言上对一些系统接口做一下封装,是为了让接口更好用。
这也导致了一个问题,导致了不同的语言,有不同的语言级别的文件访问接口(都不一样),但是,底层都是封装了系统调用接口。
所以为什么要学习OS层面的文件接口呢?
因为,这样的接口只有一套,学习完系统层面的文件接口后,可以更好的理解和使用各种语言层面的文件接口。
(2).跨平台
如果语言不提供对文件的系统接口的封装,那么所有访问文件的操作,都必须使用os的接口。像Linux,它的编写语言是C语言,系统接口是用C语言编写的,当用户编写所谓的文件代码(其他语言的),则无法在他们的平台上进行运行,也就不具备跨平台性。

4.显示器是硬件,printf向显示器打印,也是一种写入,和磁盘写入到文件,没有本质区别,Linux下,一切皆文件!

5.Linux下,一切皆文件
什么叫做文件呢?
站在系统的角度,能够被input读取,或者能够被output写入到设备的就叫做文件!
狭义文件:普通的磁盘文件。
广义文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设,都可称之为文件。

普通文件->fopen/fread->(input)你的进程的内部(内存)->fwrite->(output)文件中。

6.进程文件
在这里插入图片描述
当一个进程运行起来的时候,每个进程都会记录自己当前所处的工作路径。cwd->/home/jyf/lesson3-30

fopen 以w方式打开文件,默认先清空文件(注意:在fwrite之前)
fopen以a方式打开文件,追加,不断的向原文件增加内容

2. fwrite函数

fwrite函数
fwrite() 是 C 语言标准库中的一个文件处理函数,功能是向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作,不局限于文本文件。
函数原型:size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

(1)buffer:是一个指针,对fwrite来说,是要获取数据的地址;
(2)size:要写入内容的单字节数;
(3)count:要进行写入size字节的数据项的个数;
(4)stream:目标文件指针;
(5)返回实际写入的数据项个数count。
说明:写入到文件的哪里? 这个与文件的打开模式有关,如果是w+,则是从file pointer指向的地址开始写,替换掉之后的内容,文件的长度可以不变,stream的位置移动count个数;如果是a+,则从文件的末尾开始添加,文件长度加大。
fseek对此函数有作用,但是fwrite 函数写到用户空间缓冲区,并未同步到文件中,所以修改后要将内存与文件同步可以用fflush(FILE *fp)函数同步。
注意:返回值随着调用格式的不同而不同:

(1) 调用格式:fwrite(buf, sizeof(buf), 1, fp);
成功写入返回值为1(即count)
(2)调用格式:fwrite(buf, 1, sizeof(buf), fp);
成功写入则返回实际写入的数据个数(单位为Byte)

注意事项:
写完数据后要调用fclose()关闭流,不关闭流的情况下,每次读或写数据后,文件指针都会指向下一个待写或者读数据位置的指针。

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
    
int main()    
{    
  FILE* fp = fopen("log.txt","w");    
  if(fp == NULL)    
  {    
    perror("fopen");    
    return 1;    
  }    
  //文件操作    
  const char* s1 = "hello fwrite\n";    
  fwrite(s1,strlen(s1),1,fp);    
  //不要+1,\0结尾是C语言的规定,文件用遵循吗?不用,文件要保存的是有效数据    
  //fwrite(s1,strlen(s1+1),1,fp);    
                                                                                                                                                                                                                 
  const char* s2 = "hello fprintf\n";    
  fprintf(fp,"%s",s2);    
  const char* s3 = "hello fputs\n";    
  fputs(s3,fp);                                                                            
                                                         
  fclose(fp);                               
  //while(1)          
  //{                                               
    //sleep(1);                          
  //}                        
  return 0;                                 
}    

一个字符串是以’\0’结尾的,这是语言层面的东西,将一个字符串写入到文件中时,是不需要将’\0’写入到文件中的,文件保存的是有效数据。

3.按行读取文件内容

#include <stdio.h>    
#include <stdlib.h>    
#include <string.h>    
#include <unistd.h>    
    
int main(int argc,char* argv[])    
{    
  if(argc!=2)    
  {    
    perror("argc error\n");    
    return 1;    
  }    
    
  FILE* fp = fopen(argv[1],"r");    
  if(fp == NULL)                                                                                                                                                                                                 
  {    
    perror("fopen");    
    return 1;    
  }    
  char line[64];    
  //按行读取    
  while(fgets(line,sizeof(line),fp))    
  {    
    fprintf(stdout,"%s",line);    
  }    
    
  fclose(fp);    
  return 0;    
}  

运行结果:

[jyf@VM-12-14-centos lesson3-30]$ ./myfile1 Makefile
myfile1:myfile1.c
	gcc -o $@ $^
.PHONY: clean
clean:
	rm -f myfile1 

4.只传一个参数,让它表示不同的状态,展现不同的结果

#include <stdio.h>    
#include <string.h>    
    
//用int中的不重复的一个bit,就可以标识一种状态    
#define ONE 0x1 //0000 0001    
#define TWO 0x2 //0000 0010    
#define THREE 0x4 //0000 0100    
    
void show(int flags)    
{    
  if(flags & ONE) printf("hello one\n"); //0000 0011 & 0000 0001    
  if(flags & TWO)  printf("hello two\n");    
  if(flags & THREE) printf("hello three\n");    
}    
    
int main()    
{    
  show(ONE);    
  show(TWO);    
  show(ONE | TWO); //0000 0001 | 0000 0010 = 0000 0011    
  show(ONE |TWO | THREE);                                                                                                                                                                                        
  return 0;    
}  

运行结果:

[jyf@VM-12-14-centos lesson3-30]$ ./myfile2
hello one
hello two
hello one
hello two
hello one
hello two
hello three

5.系统层面的那一套文件接口

5.1 open函数

open() 系统调用
通过open()系统调用来打开文件并获得一个文件描述符。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open (const char *name, int flags);
int open (const char *name, int flags, mode_t
mode);

open() 系统调用将路径名name 给出的文件与一个成功返回的文件描述符相关联,文件位置指针被设定为零,而文件则根据flags给出的标志位打开

open()的flags参数
flags参数必须是以下之一:O RDONLY,O_WRONLY,O_RDWR。这些参数各自指定要用只读,只写还是读写模式打开文件。
举例来说,下面的代码以只读方式打开文件/home/kidd/madagascar。

int fd = open (‘‘/home/jyf/log.txt’’, O_RDONLY);
if (fd<0)
{
	perror("open");
	return 1;
}

以只写方式打开的文件不能被读取,反之亦然。进程必须要有足够的权限才能调用open() 系统调用对文件进行访问。
flags 参数可以和以下一个或多个值进行按位或运算,用以修改打开文件请求的行为。
O_APPEND 文件已追加模式下打开。就是说,在每次写操作之前,文件位置指针将被置于文件末尾。即使在进程刚刚完成写操作并改变文件位置指针之后,如有另一进程开始写操作,情形也是如此。
O_ASYNC 当指定文件可写或者可读时产生一个信号(默认是SIGIO)。这个标志仅用于终端和套接字,不能用于普通文件。
O_CREAT 当name指定的文件不存在时,将由内核来创建。如果文件已存在用于在opendir()内部使用。
O_DIRECT 打开文件用于直接I/O
O_EXCL 和O_CREAT一起给出的时候,如果由name给定的文件已经存在,则open()调用失败。用来防止文件创建时出现竞争。
O_LARGEFILE 给定文件打开的时候将使用64位偏移量,这样大于2G的文件也能被打开。
O_NOCTTY 如果给定的name指向一个终端设备,它将不会成为这个进程的控制终端,即使该进程目前没有控制终端。
O_NOFOLLOW 如果name是一个符号链接,opne()调用会失败。
O_NONBLOCK 如果可以,文件 将在非阻塞模式下打开。
O_SYNC 打开文件用于同步I/O。
O_TRUNC 如果文件存在,且为普通文件,并且允许写,将文件的长度截断为0。

  #include <stdio.h>    
  #include <sys/types.h>    
  #include <fcntl.h>    
  #include <string.h>    
  #include <sys/stat.h>    
  #include <unistd.h>    
      
  int main()    
  {    
    umask(0); //默认的权限掩码为2,此处将其置为0,则当我们新创建一个文件的时候,文件的权限为0666   
    int fd = open("log.txt",O_WRONLY|O_CREAT,0666);//rw-rw-rw-    
      
    //int fd = open("log.txt",O_RDONLY);    
    if(fd<0)    
    {    
      perror("open");    
      return 1;    
    }                                                                                                                                                                                        
    //open success    
    printf("open success,fd:%d\n",fd);    
      
    close(fd);    
    return 0;    
  }    

除非创建了新文件,否则mode参数会被忽略;而当O_CREAT给出时则需要。

mode参数
S_IRWXU 所有者拥有读写和执行权限
S_IRUSR 所有者拥有读权限
S_IWUSR 所有者拥有写权限
S_IXUSR 所有者拥有执行权限
S_IRWXG 组拥有者读写和执行权限
S_IRGRP 组拥有者读权限
S_IWGRP 组拥有者写权限
S_IXGRP 组拥有者执行权限
S_IRWXO 任何其他人都有读写和执行的权限
S_IROTH 任何其他人都有读权限
S_IWOTH 任何其他人都有写权限
S_IXOTH 任何其他人都有执行权限

5.2 系统接口演示

#include <stdio.h>      
#include <sys/types.h>      
#include <fcntl.h>      
#include <string.h>      
#include <sys/stat.h>      
#include <unistd.h>      
      
int main()      
{      
  umask(0);      
  //int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//rw-rw-rw-      
        
  int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);//rw-rw-rw-      
  //int fd = open("log.txt",O_RDONLY);      
  if(fd<0)      
  {      
    perror("open");      
    return 1;      
  }      
  //const char* s = "hello write";      
  const char* a = "aaa\n";      
  write(fd,a,strlen(a));//这里的strlen(a)不用+1,语言层面字符串是以\0结尾,但系统层面没这个规定                                                                                                                  
                                                                                                            
  //open success                                                                                            
  printf("open success,fd:%d\n",fd);                                                                        
                                                                                                            
  close(fd);                                                                                                
  return 0;                                                                                                 
}    
#include <stdio.h>    
#include <sys/types.h>    
#include <fcntl.h>    
#include <string.h>    
#include <sys/stat.h>    
#include <unistd.h>    
    
int main()    
{    
  //umask(0);    
  //int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//rw-rw-rw-    
    
  //int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);//rw-rw-rw-    
  int fd = open("log.txt",O_RDONLY);    
  if(fd<0)    
  {    
    perror("open");    
    return 1;    
  }    
  //const char* s = "hello write";    
  //const char* a = "aaa\n";    
  //write(fd,a,strlen(a));//这里的strlen(a)不用+1,语言层面字符串是以\0结尾,但系统层面没这个规定    
    
  char buffer[64];    
  memset(buffer,'\0',sizeof(buffer));    
  read(fd,buffer,sizeof(buffer));// log.txt文件为aaa,此处将fd指向的文件内容read到buffer中    
      
  printf("%s",buffer);//aaa                                                                                                                                                                                      
  //open success                                          
  printf("open success,fd:%d\n",fd);                      
                                                          
  close(fd);                                              
  return 0;                                               
}              

6.文件描述符 file descriptor(fd)

6.1 初识

stdin的文件描述符默认为0
stdout的文件描述符默认为1
stderr的文件描述符默认为2

FILE *fopen(const char *path, const char *mode);

在这里插入图片描述

FILE是一个struct结构体,由C标准库提供,一般内部会有多种成员。
C文件 库函数 内部一定要有系统调用!
在系统角度,认FILE,还是认fd? 只认fd
说明FILE结构体中,必定封装了fd。
证明如下:

int main()    
{    
  printf("stdin:%d\n",stdin->_fileno);    
  printf("stdout:%d\n",stdout->_fileno);    
  printf("stderr:%d\n",stderr->_fileno);                                                                                                                                                                         
  return 0;    
} 

运行结果:

[jyf@VM-12-14-centos lesson4-7]$ ./myfile1
stdin:0
stdout:1
stderr:2

stdout

#include <stdio.h>    
#include <stdlib.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main()    
{    
  fprintf(stdout,"hello stdout\n"); //往显示器上打印hello stdout     
    
  //stdout的文件描述符默认为1,所以,也可以下面的方式在显示器上打印    
  const char* s = "hello 1\n";    
  write(1,s,sizeof(s));                                                                                                                                                                                          
  return 0;    
}    

运行结果为:

[jyf@VM-12-14-centos lesson4-7]$ ./myfile1
hello stdout
hello 1

stdin

//int main()    
//{    
//  int a =0;    
//  fscanf(stdin,"%d",&a);    
//  printf("%d\n",a);    
//  return 0;    
//}    
    
int main()    
{    
    char input[16];        
    ssize_t s = read(0,input,sizeof(input)); //read之后默认会在末尾加'\n'       
    if(s>0)        
    {        
      input[s] = '\0';        
      printf("%s",input);                                                                                                                                                                                            } 
  return 0;
} 

6.2 具体过程

进程要访问文件,必须先打开文件!
一个进程可以打开多个文件吗?
一般而言 进程:打开的文件 = 1:n的
文件要被访问,前提是加载到内存中,才能被直接访问。

进程:打开的文件 = 1:n的,如果是多个进程都打开自己的文件呢?
系统中会存在大量被打开的文件!所以,OS要不要把如此之多的文件也管理起来呢?要的,先描述,再组织

在内核中,如何看待打开的文件?OS内部要为了管理每一个被打开的文件,构建
struct file
{
struct file* next;
struct file* prev;
//包含了一个被打开文件的几乎所有内容(不仅仅包含属性)
}
创建struct file的对象,充当一个被打开的文件,如果有很多呢?再用双链表组织起来。

在这里插入图片描述

根据上图,我们可以知道我们是如何访问到我们想访问的文件的。
举个例子:
fwrite()->FILE*->fd->write->write(fd,…)->自己执行操作系统内部的write方法->能找到进程的task_struct->fs->files_struct->fd_array[ ]->fd_array[fd]->struct file->内存文件被找到了!->操作。
详细描述:
我们执行fwrite方法向某个文件写入,具体是怎么找到那个文件进行操作的呢?
调用fwrite函数向某个文件进行写入,操作系统会在文件的FILE
中给其找到并分配最近的,没被占用的文件描述符fd,我们知道fwrite是语言层面的,要向硬件写入,它内部必定是封装了系统调用的,也就是write函数,将fd给到write函数,在执行write函数,此进程的task_struct结构体中有一个struct files_struct files指针,指向struct files_struct,struct files_struct里面有一个struct file array[]数组,数组里面保存的是OS给每个打开的文件都分配的struct file,write将拿到的fd,填入到struct file* array[]数组中,通过struct file* array[fd]便可找到我们要操作的文件的struct file[],在通过它内部的信息找到它对应的文件所在位置,然后进行对应的操作即可!

下文我们继续文件描述符的理解!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值