Linux 文件系统编程

系统调用(System Call):
所有的操作系统都提供多种服务的入口点,程序由此向内核请求服务。这些可直接进入内核的入口点被称为系统调用。

为什么用户程序不能直接访问内核提供的服务?
在Linux中,为了更好地保护内核空间,程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。

“文件”这个名词不陌生,什么是文件?
系统资源(内存、硬盘、一般设备、进程间通信的通道等)的一个抽象
对系统资源进行访问的一个通用接口。

采用这种“文件”的方式有什么好处?
对资源提供通用的操作接口,可以极大地简化系统编程接口的设计。

文件描述符
所有执行I/O操作的系统调用使用文件描述符来表示打开的文件。
文件描述符是一个非负整数。
文件描述符可以表示各种类型的打开的文件。
对文件的操作只要使用文件描述符即可指定所操作的文件。
只与程序有关,与相应文件无关,每个程序都有自己的文件描述符。
从小到大分配,从当前能用的描述符中最小的开始分配。
从0开始,0(代表标准输入),1(代表标准输出),2(代表标准错误),这三个描述符默认打开。

不带缓存的I/O操作
1.open
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);
                    Returns file descriptor on success, or –1 on error

各参数及返回值的含义如下:
pathname:要打开或创建的文件名称。
flags:标志位,指定打开文件的操作方式。
mode:指定新文件的访问权限。(仅当创建新文件时才使用该参数)
返回值:若成功返回文件描述符,否则返回-1并设置变量errno的值。

flag基本取值:
O_RDOWRONLY:以只写方式打开文件。
O_RDWRNLY:以只读方式打开文件。
O_RDWR:以读写方式打开文件。
注意:上述三个常量必须指定且只能指定一个。
可选取值(部分):
O_CREAT:如果被打开的文件不存在,则自动创建这个文件。
O_EXCL:如果O_CREAT标志已经使用,那么当由pathname参数指定的文件已经存在时,open返回失败。
O_TRUNC:如果被打开的文件存在并且是以可写的方式打开的,则清空文件原有的内容。
O_APPEND:新写入的内容将被附加在文件原来的内容之后,即打开后文件的读写位置被置于文件尾。

mode可用八进制数代替,例:0777(可读可写可执行);0666(可读可写可执行)

2.read
read()系统调用从打开的文件中读取数据。

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
            Returns number of bytes read, 0 on EOF, or1 on error

各参数及返回值的含义如下:
fd:要读取的文件的描述符。
buf:读取到的数据要放入的缓冲区。
count:要读取的字节数。
返回值:若成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1并设置变量errno的值。
注意:
1. 这里的size_t是无符号整型,ssize_t是有符号整型。
2. buf指向的内存空间必须事先分配好。

3.write
write()系统调用向打开的文件写数据。

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
            Returns number of bytes written, or1 on error  

各参数及返回值的含义如下:
fd:要写入的文件的描述符。
buf:要写入的数据所存放的缓冲区。
count:要写入的字节数。
返回值:若成功返回已写的字节数,出错则返回-1并设置变量errno的值。

4.close
close()系统调用关闭一个打开的文件。

#include <unistd.h>
int close(int fd);
            Returns 0 on success, or1 on error 

各参数及返回值的含义如下:
fd:要关闭的文件的描述符。
返回值:若成功返回0,出错则返回-1。
注:当一个进程终止时,内核会自动关闭它所有打开的文件。

5.lseek
lseek系统调用可以改变文件偏移量(File Offset)。文件偏移量是一个整数,表示距文件起始处的字节数。

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
Returns new file offset if successful, or1 on error

其中,参数whence必需是以下三个常量之一:
SEEK_SET:将文件偏移量设置在距文件开始处offset个字节。
SEEK_CUR:将文件偏移量设置在其当前值加offset,offset可正可负。
SEEK_END:将文件偏移量设置为文件长度加offset,offset可正可负。

复制文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define SIZE 1024

int main()
{
    // 打开要读的文件
    int fd1 = open("1.ppt", O_RDONLY);
    if (fd1 == -1)
    {
        perror ("open fd1");
        return -1;
    }


    int fd2 = open("2.ppt", O_WRONLY|O_CREAT, 0777);
    if (fd2 == -1)
    {
        perror ("open fd2");
        return -1;
    }

    int ret = 0;
    char buf[SIZE] = {0};
    while (ret = read (fd1, buf, SIZE))
    {
        if (ret == -1)
        {
            perror("read");
            break;
        }

        write (fd2, buf, ret);
    }
    printf ("文件复制完成\n");
    close (fd1);
    close (fd2);
    return 0;
}

设置文件的偏移指针

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define SIZE 1024

int main()
{
    // 打开要读的文件
    int fd = open("abc2", O_RDWR|O_CREAT, 0777);
    if (fd == -1)
    {
        perror ("open fd1");
        return -1;
    }

    // 设置这个文件的偏移指针
    lseek (fd, 20, SEEK_SET);

    char *buf = "hello";

    write (fd, buf, strlen(buf));

    close (fd);
    return 0;
}

I/O缓冲的解释(以printf()为例)
printf函数向标准输出写入多个字符,所写入的字符被放在一个用户态的缓冲区中,直到碰到一个换行符,系统才调用write函数将缓冲区中的数据写入标准输出,也就是说,在换行符之前写入的字符并不会立即出现在控制台屏幕上。

为什么要采用这种缓冲机制?
为了提高系统进行I/O操作的效率!
系统调用要请求内核的服务,会引发CPU模式的切换,期间会有大量的堆栈数据保存操作,开销比较大。如果频繁地进行系统调用,会降低应用程序的运行效率。有了缓冲机制以后,多个读写操作可以合并为一次系统调用,减少了系统调用的次数,将大大提高程序的运行效率。

所谓的标准I/O函数实际上是对底层系统调用的封装,最终读写设备或文件的操作仍需调用系统I/O函数来完成。

标准I/O函数也是使用文件描述符操作文件?
标准I/O函数并不直接操作文件描述符,而是使用文件指针。文件指针和文件描述符是一一对应的关系,这种对应关系由标准I/O库自己内部维护。文件指针指向的数据类型为FILE型,但应用程序无须关心它的具体内容。
在标准I/O中,一个打开的文件称为流(stream),流可以用于读(输入流)、写(输出流)或读写(输入输出流)。每个进程在启动后就会打开三个流,分别对应:stdin(标准输入流)、stdout(标准输出流)以及stderr(标准错误输出流)。

带缓冲的I/O操作
1.fopen
fopen用于打开一个标准I/O流

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);

fopen打开由path指定的文件,并把它与一个文件流关联起来。mode参数指定文件的打开方式。
fopen执行成功返回一个非空的FILE*指针,失败时返回NULL。

“r”或”rb”:以只读方式打开。
“w”或”wb”:以只写方式打开,并把文件长度截短为零。
“a”或”ab”:以写方式打开,新内容追加在文件尾。
“r+”或”rb+”或”r+b”:以更新方式打开(读和写)。
“w+”或”wb+”或”w+b”:以更新方式打开,并把文件长度截短为零。
“a+”或”ab+”或”a+b”:以更新方式打开,新内容追加在文件尾。

注:字母b表示文件是一个二进制文件而不是文本文件。

2.fread
fread()用于从一个文件流里读取数据。

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

数据从文件流stream读到ptr指向的数据缓冲区里。size参数指定每个数据记录的长度,nmemb给出要传输的记录的个数。函数的返回值是成功读到数据缓冲区里的记录的个数(不是字节数)。

3、fwrite
fwrite()从指定的数据缓冲区里取出数据记录,并把它们写到输出流中。它的返回值是成功写入的记录个数。函数原型如下:

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);

4、fclose
fclose()函数用于关闭指定的文件流。

#include <stdio.h>
int fclose(FILE *fp);

5、fflush
fflush()用于把文件流里所有未写出的数据立即写出。

#include <stdio.h>
int fflush(FILE *stream);  

使用这个函数可以确保在程序继续执行之前重要的数据都已经被写到磁盘上。
调用fclose函数隐含执行了一次fflush操作,所以不必在调用fclose之前调用fflush。

6、fseek
fseek()用于在文件流里为下一次读写操作指定位置。

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);  

offset和whence参数的含义和取值与lseek函数完全一样,但lseek返回的是一个off_t数值,而fseek返回的是一个整数:0表示成功,-1表示失败并设置errno指出错误。

复制文件:

#include <stdio.h>

#define SIZE 1024

int main()
{
    // FILE *fp = fopen("abc", "rb+");
    FILE *fp = fopen("1.ppt", "ab+");
    if (fp == NULL)
    {
        perror ("fopen");
        return -1;
    }

    FILE *fp1 = fopen("2.ppt", "ab+");
    if (fp1 == NULL)
    {
        perror ("fopen");
        return -1;
    }

    char buf[SIZE] = {0};

    // feof 判断是否读到文件结尾,如果读到文件结尾,它返回一个非0 的值
    int ret;
    while (ret = fread(buf, sizeof(char), SIZE, fp))
    {   
        fwrite(buf, sizeof(char), ret, fp1);
    }

    if(ret == 0 && !feof(fp))
    {
        perror ("fread");
        return -1;
    }

    printf ("文件读取结束\n");

    fclose(fp);
    fclose(fp1);
    return 0;   
}

fgetc()、getc()和getchar()
fgetc()从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件结尾或出现错误时,返回EOF。getc()和fgetc()一样,但它有可能被实现为一个宏。getchar()相当于getc(stdin)。

#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

fputc()、putc()和putchar()
fputc()把一个字符写到一个输出文件流中,它返回写入的值,如果失败,则返回EOF。类似fgetc()和getc(),putc()的作用也相当于fputc(),但它可能被实现为一个宏。putchar()相当于putc(c, stdout),它把单个字符写到标准输出。

注意:putchar和getchar都是把字符当作int类型而不是char类型来使用的,这就允许文件结尾EOF取值为-1。

#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

复制文件:

#include <stdio.h>

#define SIZE 1024

int main()
{
    // FILE *fp = fopen("abc", "rb+");
    FILE *fp = fopen("1.ppt", "ab+");
    if (fp == NULL)
    {
        perror ("fopen");
        return -1;
    }

    FILE *fp1 = fopen("2.ppt", "ab+");
    if (fp1 == NULL)
    {
        perror ("fopen");
        return -1;
    }

    char buf[SIZE] = {0};

    // feof 判断是否读到文件结尾,如果读到文件结尾,它返回一个非0 的值
    int ret;
    while (1)
    {   
        int c = fgetc(fp);
        if (c == EOF)
            break;
        fputc(c, fp1);
    }

    fclose(fp);
    fclose(fp1);
    return 0;   
}

读取数据:

#include <stdio.h>

#define SIZE 1024

typedef struct student
{
    int  id;
    char name[20];
}STU;


void write_data(STU *a)
{
    FILE *fp = fopen("student", "ab+");
    if (fp == NULL)
    {
        perror ("fopen");
        return;
    }

    // 要写入个数
    int len = sizeof(a)/sizeof(a[0]);
    fwrite(&len, sizeof(int), 1, fp);
    int i;
    for (i = 0; i < len; i++)
    {
        // 写入数据长度
        len = sizeof(a[i]);
        fwrite(&len, sizeof(int), 1, fp);

        // 写入数据
        fwrite(&a[i], sizeof(STU), 1, fp);
    }

    fclose(fp);
}

void read_data()
{
    FILE *fp = fopen("student", "ab+");
    if (fp == NULL)
    {
        perror ("fopen");
        return;
    }

    // 读记录的个数
    int count;
    fread (&count, sizeof(int), 1, fp);

    printf ("记录个数是:%d\n", count);
    int i;
    STU tmp;
    for (i = 0; i < count; i++)
    {
        int len;
        fread (&len, sizeof(int), 1, fp);

        // 读取数据
        fread (&tmp, len, 1, fp);

        printf ("id = %d, name = %s\n", tmp.id, tmp.name);
    }

    fclose(fp);
}

int main()
{


    int i;
    STU a[20];
    for (i = 0; i < 20; i++)
    {
        a[i].id = i;
        sprintf (a[i].name, "zhang%d", i);
    }

    // 写入数据
    // write_data(a);

    // 读数据
    read_data();

    return 0;   
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值