嵌入式 Linux系统编程(一)——文件IO

一、文件IO概念

        linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作。不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据。这种操作不是ASCI标准的,与系统有关,移植有一定的问题。而带缓存的是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨OS,成为ASCI标准,称为标准IO不带缓存的方式频繁进行用户态 和内核态的切换,高效但是需要程序员自己维护;带缓冲的方式因为有了缓冲区,不是非常高效,但是易于维护。由此,不带缓冲区的通常用于文件设备的操作,而带缓冲区的通常用于普通文件的操作。

    文件平时存储在块存储设备中的文件系统中(静态文件),open打开一个文件时,linux内核在进程中建立一个打开文件的数据结构,记录打开的文件的信息;内核在内存中申请建立一段内存,并将静态文件的内容从块存储设备读取到特定地址管理存放(动态文件)。

    打开文件后对这个文件的读写操作都是针对内存中的动态文件,当对动态文件进行读写后,动态文件和块存储设备中的静态文件不同步,close时关闭动态文件,内核将内存中的动态文件的内容同步到块存储设备的静态文件。

二、文件描述符

    文件描述符是一个非负整数,用来标识一个进程中打开或创建的文件。当打开一个现有文件或创建一个新文件时,内核向应用程序进程返回一个文件描述符。当读或写一个文件时,使用opencreat返回的文件描述符标识该文件,将其作为参数传递给readwrite等操作函数文件描述符的作用域限于当前应用程序的进程,文件关闭close后,文件描述符将被释放。在遵从POSIX的应用程序中,文件描述符012分别对应STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO,因此一个应用程序进程中文件描述符总是从3开始的。

三、常用文件IO函数

1open

            #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);

Open函数返回打开、创建文件的文件描述符,如果失败返回-1

Flags

O_RDONLY   //只读打开

O_WRONLY   //只写打开

O_RDWR    //读、写打开

O_APPEND   //每次写时都追加到文件的尾端

O_CREAT   //若此文件不存在,则创建它。使用时,需要第三个参数mode

O_EXCL   //如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件

O_TRUNC  //如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0

O_NONBLOCK  //如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次操作和后续的I/O操作设置非阻塞模式。只用于设备文件,不能用于普通文件。

O_SYNC    //使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O

Mode

使用四个数字指定创建文件的权限,与linux的权限设置相同,如0755

 

2close


            #include <unistd.h>

       int close(int fd);

关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。

 

3read

 #include <unistd.h>

 

           ssize_t read(int fd, void *buf, size_t count);

Read成功返回读取的字节数,失败返回-1

 

4write

         #include <unistd.h>

 

        ssize_t write(int fd, const void *buf, size_t count);

Write成功返回写入的字节数,失败返回-1

 

5、lseek

    每一个已打开的文件都有一个读写位置,当打开文件时通常其读写位置是指向文件开头,若是以附加的方式打开文件(O_APPEND),则读写位置会指向文件尾。当read()write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置。

#include <sys/types.h>

       #include <unistd.h>

 

       off_t lseek(int fd, off_t offset, int whence);

Lseek成功,返回当前的位置,即当前位置距离文件开头的字节数,失败返回-1

Offset:偏移量

Whence:偏移基址

SEEK_SET:将读写位置指向文件头后再增加offset个位移量

SEEK_CUR以目前的读写位置往后增加offset个位移量

SEEK_END将读写位置指向文件尾后再增加offset个位移量

A 欲将读写位置移到文件开头时

lseekint fd,0,SEEK_SET);

返回0

B欲将读写位置移到文件尾时

lseekint fd0,SEEK_END);

返回文件长度

C想要取得目前文件位置时

lseekint fd0,SEEK_CUR);

返回当前文件指针相对于文件开头的偏移量

D、计算文件长度

int get_file_length(const char *filename)

{

    unsigned int n = 0;

    unsigned int fd = open(filename, O_RDONLY);

    if(fd < 0)

    {   

        fprintf(stderr, "error:%s PID: %d\n", strerror(errno), getpid());

        return -1;

    }   

    n = lseek(fd, 0, SEEK_END);

    close(fd);

    return n;

}

 

E、创建空洞文件

int create_null_file(const char *filename, unsigned int len)

{

 

    unsigned int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);

    if(fd < 0)

    {   

        fprintf(stderr, "error:%s PID: %d", strerror(errno), getpid());

        return -1;

    }   

    lseek(fd, len, SEEK_SET);

    write(fd, "\0",1);

    close(fd);

    return 0;

}

四、错误信息

errno就是error number

如果程序代码中包含 #include <errno.h>,函数调用失败的时候,系统会自动用用错误代码填充errno这个全局变量,errno全局变量可以获得失败原因。函数调用失败是否会设置errno全局变量由函数决定,并不是所有函数调用失败都会设置errno变量。

#include <string.h>

 

char *strerror(int errnum);

fprintf(stderr,"error :%s, PID: %d ",strerror(errno),getpid());

 

根据errnum错误码返回一个指向描述errnum错误码信息的字符串指针。

 

 #include <stdio.h>

 

 void perror(const char *s);

perror ( )用来将上一个函数发生错误的原因输出到标准错误(stderr),参数s 所指的字符串会先打印出,后面再加上错误原因 字符串。

 

五、文件共享

文件共享的是三种实现方式:

1、同一个进程中多次打开同一个文件

2、不同进程中多次打开同一个文件

3、Dupdup2让进程复制文件描述符

    同一个进程中多次打开同一个文件,返回的文件描述符不同,同时对这个文件进行写操作时,分别写入内容,后边写入的内容将覆盖前边写入的内容,此时不同的文件描述符拥有自己的文件指针。当open创建、打开文件时采用O_APPEND,则不同的文件描述符的不同文件指针会实现同步。O_APPEND是原子操作的。所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断

文件描述符的复制

#include <unistd.h>

 

int dup(int oldfd);//返回新分配的文件描述符,失败返回-1

int dup2(int oldfd, int newfd);//newfd为指定的新的文件描述符,返回新分配的文件描述符,失败返回-1

dup函数复制的文件描述符不同,但文件指针相同,所以对文件的操作是原子操作的。

 

六、文件IO与标准IO库的区别

    文件IO与标准IO库的区别:文件IOlinux系统的API,标准IO库是C语言库函数,标准IO库函数由linux API封装而来,函数内部通过调用linux API完成。API在不同的操作系统之间不能通用,C语言库函数可以在不同操作系统之间移植。文件IO函数不带缓存,标准IO库函数带缓存。