(2.2)文件和目录操作——Linux的文件IO操作

时间:2021-12-27 00:43:55


文章目录

  • ​​1.Linux系统调用​​
  • ​​2.为什么用户程序不直接访问系统内核提供的服务?​​
  • ​​3.文件描述符​​
  • ​​(1)内核如何区分和引用特定的文件?​​
  • ​​(2) 一个进程启动时, 通常会打开3个文件​​
  • ​​4.Linux文件IO函数说明​​
  • ​​(1)open函数​​
  • ​​(2)creat函数​​
  • ​​(3)close函数​​
  • ​​(4)read函数​​
  • ​​(4)write函数​​
  • ​​5.ioctl函数​​

1.Linux系统调用

  • 系统调用: 操作系统提供给用户程序调用的一组“特殊” 接口, 用户程序可以通过这组“特殊” 接口来获得操作系统内核提供的服务
  • 系统调用和普通调用的区别:
    系统调用是由OS内核提供的,运行于内核态;
    普通的函数调用是由函数库或用户自己提供的,运行于用户态;
    有一些标准的C语言库函数,在Linux系统上的实现,都是通过系统调用来完成的。

2.为什么用户程序不直接访问系统内核提供的服务?

  • 为了更好地保护内核空间, 将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和
    用户态) , 它们分别运行在不同的级别上, 在逻辑上是相互隔离的。 因此, 用户进程在通常情况下不允许访问内核数据, 也无法使用内核函数, 它们只能在用户空间操作用户数据, 调用用户空间的函数
  • 进行系统调用时, 程序运行空间从用户空间进入内核空间, 处理完后再返回到用户空间
  • (2.2)文件和目录操作——Linux的文件IO操作

  • 系统调用并不是直接与程序员进行交互的, 它仅仅是一个通过软中断机制向内核提交请求, 以获取内核服务的接口。
    原理:进程先用适当的值填充寄存器,然后调用一个特殊的指令,该指令会跳转到事先定义的内核中的位置,该位置是用户进程可读,但是不可写的。
    在Intel中,这是由中断0x80实现的,此时硬件就知道此时是在操作系统的内核中运行,有了内核的权限就可以使用内核的服务了,该内核的位置是system_call,会检查系统的调用号,该号码告诉内核我们的进程是在请求哪种服务,然后查看系统调用表,找到所调用的内核函数的入口地址, 接着调用函数,返回后做一些系统检查,最后在返回到进程
  • 在实际使用中程序员调用的通常是用户编程接口——API。

(2.2)文件和目录操作——Linux的文件IO操作

  • Linux中的系统调用包含在Linux的libc库中, 通过标准的C函数调用方法可以调用
    在跳转到内核态时,由系统调用处理程序和服务例程,然后依次返回给用户态
  • 系统命令相对API更高了一层, 它实际上是一个可执行程序, 它的内部调用了用户编程接口(API) 来实现相应的功能。
  • eg:
# cp /floppy/TEST /temp/test

(2.2)文件和目录操作——Linux的文件IO操作

3.文件描述符

(1)内核如何区分和引用特定的文件?

  • 通过文件描述符。 文件描述符是一个非负的整数, 它是一个索引值, 并指向在内核中每个进程打开文件的记录表。 当打开一个现存文件或创建一个新文件时, 内核就向进程返回一个文件描
    述符;当需要读写文件时, 也需要把文件描述符作为参数传递给相应的函数。

(2) 一个进程启动时, 通常会打开3个文件

-----标准输入 描述符为0       默认是键盘
------标准输出 描述符为1 默认是屏幕
------标准出错处理 描述符为2 默认是屏幕
  • 在POSIX应用程序中,整数0,1,2被代换成了符号常数,这些常数都是定义在unstd.h中;
  • 文件描述符的范围是:0-OPENMAX,允许每个进程打开的文件数0-OPENMAX

4.Linux文件IO函数说明

open():用于打开或创建文件, 可以指定文件的属性及用户的权限等各种参数

creat():打开一个文件, 如果文件不存在, 则创建它

close():用于关闭一个被打开的文件。 当一个进程终止时, 所有被它打开的文件都
由内核自动关闭, 很多程序都使用这一功能而不显示地关闭一个文件

read():用于将从指定的文件描述符中读出的数据放到缓存区中, 并返回实际读入
的字节数。 若返回0, 则表示没有数据可读, 即已达到文件尾。 读操作从文件的当
前指针位置开始

write(): 用于向打开的文件写数据, 写操作从文件的当前指针位置开始。 对磁盘
文件进行写操作, 若磁盘已满或超出该文件的长度, 则write()函数返回失败

(1)open函数

头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

函数原型
int open( const char * pathname, int flags);
int open( const char * pathname,int flags, mode_t mode);

mode:
指定文件的权限是mode的值,一般是8进制的权限码,0777表示:文件所有者,文件用户组,其他用户的权限是rwx。
只有创建新文件的时候,才设定mode



函数说明
参数pathname 指向欲打开的文件路径字符串。

(1)下面三个是主类标识:这三种方式互斥,即不能同时使用。但可以与下类的副类标识来组合使用,利用|运算符
下列是参数flags 常用的标识:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件。上述三种标识是互斥的,也就是不可同时使用,但可与下列的标识利用OR(|)运算符组合。

(2)下面的是副类标识
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_TRUNC 若文件存在并且以可写的方式打开时,此标识会令文件长度清为0,而原来存于该文件的资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
O_EXCL: 如果同时指定 O_CREAT,而该文件又是存在的,报错;也可以测试一个文件是否存在,不存在则创建。

返回值
文件打开成功返回文件的描述符,失败返回
  • eg:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FILE_PATH "./text.txt"

int main(void)
{
int fd;
if (fd=open(FILE_PATH, O_RDWR|O_CREAT|O_EXECL, 0666)<0)
{
printf("open error\n");/*若打开一个已经存在的文件*/
exit(-1);
}
else
printf("Open success\n");/*不存在,则创建该文件*/
return 0;
}

(2)creat函数

creat(建立文件)

头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

函数原型
int creat(const char * pathname, mode_t mode)

参数说明
参数pathname指向欲建立的文件路径字符串。
creat()相当于使用下列的调用方式调用open():
open(const char * pathname ,(O_CREAT|O_WRONLY|O_TRUNC))

返回值
creat()会返回新的文件描述词,若有错误发生则会返回-1,并把错误代码设给errno

附加说明
creat函数的一个不足之处是它以只写方式打开所创建的文件

(3)close函数

close(关闭文件),和open()一一对应

头文件
#include<unistd.h>

函数原型
int close(int fd);
fd:
需要关闭的文件描述符,调用该函数时,Linux会对内核文件描述符表对应的文件表项和索引节点
表项进行相应的处理,来完成关闭文件的操作;
在进程关闭文件后,就不能通过open()和creat()所返回的文件描述符fd来操作该文件

函数说明
当使用完文件后若已不再需要则可使用close()关闭该文件, close()会让数据写回磁盘,并释放
该文件所占用的资源。参数fd为先前由open()或creat()所返回的文件描述符;

返回值
若文件顺利关闭则返回0,发生错误时返回-1。

附加说明
虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并检查返回值。

(4)read函数

read(由已打开的文件读取数据)

头文件
#include<unistd.h>

定义函数
ssize_t read(int fd,void * buf ,size_t count);

函数说明
read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数
count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回
0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移
动;

返回值
成功返回读取的字节数,出错返回-1。
  • read函数实际读到的字节数少于要求读的字节数时:
    (a) 读普通文件,在读到要求字节数之前就到达文件尾;
    (b)当从终端设备读,通常一次最多读一行;
    (c)当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数;
    (d)某些面向记录的设备,如磁带,一次最多返回一个记录;
  • 读操作完成后,文件的当前位置将从读之前的位置加上实际读的字节数
  • 当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期

(4)write函数

write(将数据写入已打开的文件内)

头文件
#include<unistd.h>

函数原型
ssize_t write (int fd,const void * buf,size_t count);

函数说明
write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移
动;

返回值
如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。 write出错的
原因可能是磁盘满、没有访问权限、或写超过文件长度限制等等

附加说明
将数据写入已打开的文件内。对于普通文件,写操作从文件当前位置开始写(除非打开文件时指定了
O_APPEND选项)。写操作完成后,文件的当前位置将从写之前的位置加上实际写的字节数
  • 数据无法一次性读完时
    第二次读buf中数据时,读位置指针并不会自动移动,
    按如下格式实现读位置移动: write(fp, p1+len, (strlen(p1)-len),直至指针恢复
  • Write一次可以写的最大数据范围是8192
    写入数据大小最好小于buff中的值
    Count参数值大于SSIZE_MAX,则write调用的结果未定义
    Count参数值为0时, write调用会立即返回0这个值
  • Write调用返回时,内核已经将缓冲区所提供的数据复制到内核的缓冲区,但是无法保证数据已经写出到预定的目的地
    通常指的是:物理设备作为目的地,因为系统调用的速度比硬盘写入的速度要快,所以系统内核采用延后写入的机制来处理,内核会在后台搜集所有脏缓冲区(有数据写入的缓冲区),将这些缓冲区安排成最佳顺序,接着写入磁盘中,内核安排写入顺序主要是基于性能的考虑,而不是根据用户的执行顺序,即与缓冲区写入的先后无关。
  • eg:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
char buf[100];
int num=0;
if (num=read(STDIN_FILENO, buf ,10)==-1)/*STDIN_FILENO表示接受键盘的输入*/
{
printf("read error");
error(-1);
}
else
write(STDIN_FILENO, buf,num);/*将键盘的输入又输出到屏幕上*/
return 0;
}

5.ioctl函数

ioctl(设备驱动程序中对设备的I/O通道进行管理)

头文件
#include<sys/ioctl.h>

定义函数
int ioctl(int fd, int cmd, ...);
fd:open()函数打开的文件标识符
cmd:用户程序对设备的控制命令
...:省略命令,一般最多一个

函数说明
ioctl()能对一些特殊的文件(主要是设备)进行一些底层参数的操作。许多字符设备都使用ioctl请求来完成对
设备的控制;

返回值
成动返回0。当有错误发生时则返回-1,错误代码存入errno中

附加说明
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性
进行控制,例如串口的传输波特率、马达的转速等等。
  • 应用层与驱动函数的ioctl之间的联系

(2.2)文件和目录操作——Linux的文件IO操作

  • 在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作
  • ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径
    命令与设备是一一对应的;
    内核提供宏,用能够理解的字符串来生成命令码;或者利用命令码得到用户能够理解的字符串,已表明对应设备类型,序列号,数据传送方向,和数据传输尺寸
  • (2.2)文件和目录操作——Linux的文件IO操作

  • "幻数"是一个字母,数据长度也是8,用一个特定的字母来标明设备类型