【嵌入式学习历程12】Linux文件编程

来源:互联网 发布:java math 绝对值 编辑:程序博客网 时间:2024/05/17 07:44

Linux下皆文件,短短几个字就道出了文件系统在Linux下的重要性。在Linux操作系统中,它对一切资源的管理归根到底都是对文件的操作。
大背景
在现代操作系统中,要利用大量的程序和数据,由于内存容量有限,且不能长期保存,于是人们想出了把这些数据以文件的形式放在外存中,需要的时候再将它们调入内存,从此就有了文件系统,它负责管理外存上 的文件,并把存取、共享和保护等手段提供给用户,这样就方便了用户,保证了文件的安全性,还提高了系统资源您的利用。
为什么用户程序不能直接访问系统内核提供的服务呢?
由于在Linux中,为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。
因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。

Linux文件分类
普通文件
目录文件
设备文件
管道文件
链接文件

文件描述符
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。
一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理 。这三个文件对应的文件描述符
分别为0、1、2,也就是宏替换STDIN_FILENO 、STDOUT_FILENO 、STDERR_FILENO

系统调用
每个操作系统都在内核里有一些内建的函数库,这些函数可以用来完成一些系统系统调用把应用程序的请求传给内核,调用相应的的内核函数完成所需的处理,将处理结果返回给应用程序,如果没有系统调用和内核函数,用户将不能编写大型应用程序,及别的功能,这些函数集合起来就叫做程序接口或应用编程接口(API),我们要在这个系统上编写各种应用程序,就是通过这个API接口来调用系统内核里面的函数。如果没有系统调用,那么应用程序就失去内核的支持。
系统调用并不是直接与程序员进行交互的,它仅仅是一个通过软中断机制向内核提交请求,以获取内核服务的接口。在实际使用中程序员调用的通常是API。
系统命令相对API更高了一层,它实际上一个可执行程序,它的内部引用了用户编程接口(API)来实现相应的功能
这里写图片描述
不带缓存的I/O操作 与 带缓冲的I/O操作 比较
不带缓存,不是直接对磁盘文件进行读取操作,像read()和write()函数,它们都属于系统调用,只不过在用户层没有缓存,所以叫做无缓存I/O,但对于内核来说,还是进行了缓存,只是用户层看不到罢了。如果你要写入数据到文件上时(就是写入磁盘上),内核先将数据写入到内核中所设的缓冲储存器,假如这个缓冲储存器的长度是100个字节,你调用系统函数写操作时,设每次写入长度count=10个字节,那么你几要调用10次这个函数才能把这个缓冲区写满,此时数据还是在缓冲区,并没有写入到磁盘,缓冲区满时才进行实际上的I/O操作,把数据写入到磁盘上。
那么,既然不带缓存的操作实际在内核是有缓存器的,那带缓存的IO操作又是怎么回事呢?
带缓存I/O也叫标准I/O,符合ANSI C 的标准I/O处理,不依赖系统内核,所以移植性强,我们使用标准IO操作很多时候是为了减少对read()和write()的系统调用次数,带缓存IO其实就是在用户层再建立一个缓存区,这个缓存区的分配和优化长度等细节都是标准I/O库代你处理好了,不用去操心,还是用上面那个例子说明这个操作过程:
上面说要写数据到文件上,内核缓存(注意这个不是用户层缓存区)区长度是100字节,我们调用不带缓存的IO函数write()就要调用10次,这样系统效率低,现在我们在用户层建立另一个缓存区(用户层缓存区或者叫流缓存),假设流缓存的长度是50字节,我们用标准C库函数的fwrite()将数据写入到这个流缓存区里面,流缓存区满50字节后在进入内核缓存区,此时再调用系统函数write()将数据写入到文件(实质是磁盘)上,看到这里,你用该明白一点,标准IO操作fwrite()最后还是要调用无缓存I/O操作write,这里进行了两次调用fwrite()写100字节也就是进行两次系统调用write()。
总结一下就是:
**无缓存IO操作数据流向路径:数据——内核缓存区——磁盘
标准IO操作数据流向路径:数据——流缓存区——内核缓存区——磁盘**

标准I/O了三种类型的缓存:
1) 全缓存。当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
2) 行缓存。当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
3) 无缓存。相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。

不带缓存的I/O对是文件描述符操作,带缓存的I/O是针对流的。
标准I/O库就是带缓存的I/O,它由ANSI C标准说明。标准I/O库代替用户处理很多细节,比如缓存分配、以优化长度执行I/O等。
使用标准I / O例程的一个优点是无需考虑缓存及最佳I / O长度的选择,并且它并不比直接调用read、write慢多少。

不带缓存的I / O 操作
主要用到6个函数,creat、open、read、write、lseek和close。
create函数创建文件
int creat(const char *pathname, mode_t mode)
参数:
pathname;文件名(路径)
mode:创建模式,常创建模式:
S_IRUSR 可读
S_IWUSR 可写
S_IXUSR 可执行
S_IXRWU 可读、可写、可执行
除用以上宏来选择创建模式,也可以用数字来表示

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>int main(){    int ret;    ret = creat("hello.txt", S_IRWXU | S_IRWXG);    if(-1 == ret)    {        perror("creat");        exit(1);    }    return 0;}

open函数
open函数是用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname是要打开或创建的文件的名字
flags参数可用来说明此函数的多个选择项
mode对于open函数而言,仅当创建新文件时才使用第三个参数
返回值:成功返回新分配的文件描述符,
出错返回-1并设置errno

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>int main(){    int fd, ret;    fd = open("hello.txt", O_RDWR | O_CREAT | O_EXCL, S_IRWXU);    if(-1 == fd)    {        perror("open");        exit(1);    }    close(fd);    return 0;}

write函数
int write(int fd, const void * buf, size_t length)
功能:
把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <unistd.h>int main(){    int fd, ret;    char buf[100] = "hello world!";    fd = open("hello.txt", O_RDWR | O_CREAT, S_IRWXU);    if(-1 == fd)    {        printf("Errno : %d\n", errno);        perror("open");        exit(1);    }    ret = write(fd, buf, sizeof("hello world!"));    if(-1 == ret)    {        perror("write");        exit(1);    }    close(fd);    return 0;}

read函数
int read(int fd, const void *buf, size_t length)
功能:
从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <unistd.h>int main(){    int fd, ret;    char buf[100] = {0};    fd = open("hello.txt", O_RDWR | O_CREAT, S_IRWXU);    if(-1 == fd)    {        printf("Errno : %d\n", errno);        perror("open");        exit(1);    }    ret = read(fd, buf, sizeof(buf));    if(-1 == ret)    {         perror("read");         exit(1);    }    printf("Read is :%s\n", buf);     close(fd);    return 0;}

lseek函数
int lseek(int fd, offset_t offset, int whence)
功能:
将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置

经典案例:文件复制

#include <stdio.h>#include <stdlib.h> #include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#define BUFFER_SIZE     64int main(int argc, char *argv[]){       if(argc != 3)               //如果参数个数不为3,则错误    {        printf("ERROR!\n");        exit(1);    }    int from_fd, to_fd, ret;    char buffer[BUFFER_SIZE] = {0};    /* 打开源文件 */    from_fd = open(argv[1], O_RDONLY);    if (-1 == from_fd)           //打开文件失败    {        perror("open1");        exit(1);    }    /* 创建并打开目的文件 */    to_fd = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);    if(-1 == to_fd)    {        perror("open2");        exit(1);    }    while(ret = read(from_fd, buffer, sizeof(buffer)))    {        ret = write(to_fd, buffer, ret);        if(ret < 0)        {            perror("write");            exit(1);        }        memset(buffer, 0, sizeof(buffer));    }    return 0;}

带缓存的I/O操作
C库函数的文件操作是独立于具体的操作系统平台的,不管是在DOS、Windows、Linux还是在VxWorks中都是这些函数。

fopen函数
FILE *fopen(const char *path, const char *mode);
参数:
path:打开的文件名(路径)
mode:打开模式
r, rb:只读模式打开,文件必须存在
r+,rb+,r+b:读写模式打开,文件必须存在
w,wb:只写模式打开,如果文件不存在则创建、存在则清空重写
w+,wb+,w+b:读写模式打开,如果文件不存在则创建、存在则清空重写
a,ab:只能在文件末尾追加数据,不存在则创建
a+,ab+,a+b:读和追加模式打开,不存在则创建

#include <stdio.h>#include <stdlib.h>int main(){    FILE *fp = fopen("hello.txt", "a");    if(NULL == fp)    {        perror("fopen");        exit(1);    }    fclose(fp);    return 0;}

fread函数
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
stream:为已打开的文件指针
ptr:指向欲存放读取进来的数据空间,读取的字符数以参数size*nmemb来决定
返回值:返回实际读取到的字符数
通常情况下,我们将size写成1;

#include <stdio.h>#include <stdlib.h>int main(){    FILE *fp;    char buf[100] = {0};    size_t ret;    fp = fopen("hello.txt", "r");    if(NULL == fp)    {        perror("fopen");        exit(1);    }    ret = fread(buf, 1, sizeof(buf), fp);    if(0 == ret)    {        perror("fread");        exit(1);    }    printf("Read From Txt : %s\n", buf);    return 0;}

fwrite函数
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
stream:为已打开的文件指针
ptr:指向欲写入的数据地址
返回值:返回实际写入的字节数目

#include <stdio.h>#include <stdlib.h>int main(){    FILE *fp;    char buf[50] = "huazai686";    size_t ret;    fp = fopen("hello.txt", "a");    if(NULL == fp)    {        perror("open");        exit(1);    }    ret = fwrite(buf, 1, sizeof(buf), fp);    if(0 == ret)    {        perror("fwrite");        exit(1);    }    fclose(fp);    return 0;}

利用上面的函数实现cat命令:

#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc, char *argv[]){    FILE *fp;    char buf[100] = {0};    size_t ret;    fp = fopen(argv[1],"r");    {        if(NULL == fp)        {            perror("fopen");            exit(1);        }    }    while (ret = fread(buf, 1, sizeof(buf) - 1, fp))    {        printf("%s",buf);        memset(buf, 0, sizeof(buf));    }        fclose(fp);    return 0;}
原创粉丝点击