c /c++复习笔记 第三天

====================

文件系统(上)

====================

  • 一系统调用
  • 二文件系统
  • 三一切皆文件
    • 1 目录文件
    • 2 设备文件
  • 五文件描述符
  • 六opencreatclose
    • 范例openc
  • 七write
    • 范例writec
  • 八read
    • 范例readc
    • 范例binaryc
    • 范例textc
    • 代码copyc
  • 九系统IO与标准IO
    • 范例sysioc
    • 范例stdioc
  • 十lseek
    • 范例seekc
  • 十一打开文件的内核数据结构
    • 范例badc
  • 十二dupdup2
    • 范例samec

一、系统调用


Created with Raphaël 2.1.0 **应用程序** **各种库**(C/C++标准库、Shell命令和脚本、X11图形程序及库) **系统调用** (内核提供给外界访问的接口函数,调用这些函数将使进程进入内核态) **内核**(驱动程序、系统功能程序)
  1. Unix/Linux大部分系统功能是通过系统调用实现的。
    如:open/close。

  2. Unix/Linux的系统调用已被封装成C函数的形式,
    但它们并不是标准C的一部分。

  3. 标准库函数大部分时间运行在用户态,
    但部分函数偶尔也会调用系统调用,进入内核态。
    如:malloc/free。

  4. 程序员自己编写的代码也可以调用系统调用,
    与操作系统内核交互,进入内核态。
    如:brk/sbrk/mmap/munmap。

  5. 系统调用在内核中实现,其外部接口定义在C库中。
    该接口的实现借助软中断进入内核。

time命令:测试运行时间
real : 总执行时间
user : 用户空间执行时间
sys  : 内核空间执行时间
strace命令:跟踪系统调用

二、文件系统


图示:fs.bmp

三、一切皆文件


  1. Linux环境中的文件具有特别重要的意义,
    因为它为操作系统服务和设备,
    提供了一个简单而统一的接口。
    在Linux中,(几乎)一切皆文件。

  2. 程序完全可以象访问普通磁盘文件一样,
    访问串行口、网络、打印机或其它设备。

  3. 大多数情况下只需要使用五个基本系统调用:
    open/close/read/write/ioctl,
    即可实现对各种设备的输入和输出。

  4. Linux中的任何对象都可以被视为某种特定类型的文件,
    可以访问文件的方式访问之。

  5. 广义的文件

1) 目录文件

# vim day01

图示:dir.bmp
c /c++复习笔记 第三天_第1张图片

2) 设备文件

A. 控制台:/dev/console
B. 声卡:/dev/audio
C. 标准输入输出:/dev/tty
D. 空设备:/de# cat /dev/tty
Hello, World !
Hello, World !

# echo Hello, World ! > /dev/tty
Hello, World !

# echo Hello, World ! > test.txt
# cat test.txt
Hello, World !
# cat /dev/null > test.txt
# cat test.txt

# find / -name perl 2> /dev/null调用

open - 打开/创建文件
creat - 创建空文件
close - 关闭文件
read - 读取文件
write - 写入文件
lseek - 设置读写位置
fcntl - 修改文件属性
unlink - 删除硬链接
rmdir - 删除空目录
remove - 删除硬链接(unlink)或空目录(rmdir)

注意:

  1. 如果被unlink/remove删除的是文件的最后一个硬链接,
    并且没有进程正打开该文件,
    那么该文件在磁盘上的存储区域将被立即标记为自由。
    反之,如果有进程正打开该文件,
    那么该文件在磁盘上的存储区域,
    将在所有进程关闭该文件之后被标记为自由。
  a -> +-----+
X b -> | ... |
X c -> +-----+
  1. 如果被unlink/remove删除的是一个软链接文件,
    那么仅软链接文件本身被删除,其目标不受影响。
       +-----+
  a -> | ... |
       +-----+
       +-----+
X b -> |  a  |
       +-----+
       +-----+
X c -> |  a  |
       +-----+

五、文件描述符


  1. 非负的整数。

  2. 表示一个打开的文件。

  3. 由系统调用(open)返回,
    被内核空间(后续系统调用)引用。

  4. 内核缺省为每个进程打开三个文件描述符:

0 - 标准输入
1 - 标准输出
2 - 标准出错

在unistd.h中被定义为如下三个宏:

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

范例:redir.c

# a.out 0o.txt 2>e.txt
  1. 文件描述符的范围介于0到OPEN_MAX之间,
    传统Unix中OPEN_MAX宏被定义为63,
    现代Linux使用更大的上限。

六、open/creat/close


#include 

int open (
    const char* pathname, // 文件路径
    int         flags,    // 状态标志
    mode_t      mode      // 权限模式(仅创建文件有效)
);  // 创建/读写文件时都可用此函数

int creat (
    const char* pathname, // 文件路径
    mode_t      mode      // 权限模式
);  // 常用于创建文件


int open (
    const char* pathname, // 文件路径
    int         flags     // 状态标志
);  // 常用于读写文件

成功返回文件描述符,失败返回-1。

flags为以下值的位或:

O_RDONLY - 只读。\
|
O_WRONLY - 只写。 > 只选一个
|
O_RDWR - 读写。/

O_APPEND - 追加。

O_CREAT - 创建。不存在即创建(已存在即直接打开,
并保留原内容,除非…),
有此位mode参数才有效。

O_EXCL - 排斥。已存在即失败。\

只选一个,
O_TRUNC - 清空。已存在即清空 / 配合O_CREAT使用
(有O_WRONLY/O_RDWR)。

O_NOCTTY - 若pathname指向一个终端设备,
则该终端不会成为调用进程的控制终端。

O_NONBLOCK - 非阻塞。若pathname指向FIFO/块/字符文件,
则该文件的打开及后续操作均为非阻塞模式。

O_SYNC - 写同步。write等待数据和属性,
被物理地写入底层硬件后再返回。

O_DSYNC - 数据写同步。write等待数据,
被物理地写入底层硬件后再返回。

O_RSYNC - 读同步。read等待对所访问区域的所有写操作,
全部物理地写入底层硬件后,再读取并返回。

O_ASYNC - 异步读写。当文件描述符可读/写时,
向调用进程发送SIGIO信号。

open/creat所返回的一定是当前未被使用的,
最小文件描述符。

一个进程可以同时打开的文件描述符个数,
受limits.h中定义的OPEN_MAX宏的限制,
POSIX要求不低于16,传统Unix是63,现代Linux是256。

#include 

int close (
    int fd // 文件描述符
);

成功返回0,失败返回-1。

范例:open.c

#include 
#include 
#include 
int main (void) {
    int fd1 = open ("open.txt",
        O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (fd1 == -1) {
        perror ("open");
        return -1;
    }
    printf ("fd1 = %d\n", fd1);
    close (fd1);
    int fd2 = open ("open.txt", O_RDONLY);
    if (fd2 == -1) {
        perror ("open");
        return -1;
    }
    printf ("fd2 = %d\n", fd2);
    close (fd2);
//  close (fd1);
    return 0;
}

操作系统可通过权限掩码(当前为0022),
屏蔽程序所创建文件的某些权限位。如:

0666 (rw-rw-rw-) & ~0022 = 0644 (rw-r–r–)

creat函数是通过调用open实现的。

int creat (const char* pathname, mode_t mode) {
return open (pathname,
O_WRONLY | O_CREAT | O_TRUNC, mode);
}

七、write


#include 

ssize_t write (
    int         fd,   // 文件描述符
    const void* buf,  // 缓冲区
    size_t      count // 期望写入的字节数
);

成功返回实际写入的字节数(0表示未写入),失败返回-1。

size_t: unsigned int,无符号整数
ssize_t: int,有符号整数

范例:write.c

#include 
#include 
#include 
#include 
int main (void) {
    int fd = open ("write.txt", O_WRONLY | O_CREAT |
        O_TRUNC, 0664);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    const char* text = "Hello, World !";
    printf ("写入内容:%s\n", text);
    size_t towrite = strlen (text) * sizeof (text[0]);
    ssize_t written = write (fd, text, towrite);
    if (written == -1) {
        perror ("write");
        return -1;
    }
    printf ("期望写入%d字节,实际写入%d字节。\n",
        towrite, written);
    close (fd);
    return 0;
}

八、read


#include 

ssize_t read (
    int    fd,   // 文件描述符
    void*  buf,  // 缓冲区
    size_t count // 期望读取的字节数
);

成功返回实际读取的字节数(0表示读到文件尾),
失败返回-1。

范例:read.c

#include 
#include 
#include 
#include 
int main (void) {
    int fd = open ("read.txt", O_RDONLY);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    char text[256] = {};
    size_t toread = sizeof (text);
    ssize_t readed = read (fd, text, toread);
    if (readed == -1) {
        perror ("read");
        return -1;
    }
    printf ("期望读取%d字节,实际读取%d字节。\n",
        toread, readed);
    text[readed / sizeof (text[0])] = '\0';
    printf ("读取内容:%s", text);
    close (fd);
    return 0;
}

二进制读写和文本读写。

范例:binary.c

#include 
#include 
#include 
int main (void) {
    int fd = open ("binary.dat",
        O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    char name[256] = "张飞";
    if (write (fd, name, sizeof (name)) == -1) {
        perror ("write");
        return -1;
    }
    unsigned int age = 38;
    if (write (fd, &age, sizeof (age)) == -1) {
        perror ("write");
        return -1;
    }
    double salary = 20000;
    if (write (fd, &salary, sizeof (double)) == -1) {
        perror ("write");
        return -1;
    }
    struct Employee {
        char name[256];
        unsigned int age;
        double salary;
    }   employee = {"赵云", 25, 8000};
    if (write (fd, &employee, sizeof (employee))==-1) {
        perror ("write");
        return -1;
    }
    close (fd);
    if ((fd = open ("binary.dat", O_RDONLY)) == -1) {
        perror ("open");
        return -1;
    }
    if (read (fd, name, sizeof (name)) == -1) {
        perror ("read");
        return -1;
    }
    printf ("姓名:%s\n", name);
    if (read (fd, &age, sizeof (age)) == -1) {
        perror ("read");
        return -1;
    }
    printf ("年龄:%u\n", age);
    if (read (fd, &salary, sizeof (salary)) == -1) {
        perror ("read");
        return -1;
    }
    printf ("工资:%.2lf\n", salary);
    if (read (fd, &employee, sizeof (employee))==-1) {
        perror ("read");
        return -1;
    }
    printf ("员工:%s %u %.2lf\n", employee.name,
        employee.age, employee.salary);
    close (fd);
    return 0;
}

范例:text.c

#include 
#include 
#include 
#include 
int main (void) {
    int fd = open ("text.txt",
        O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    char name[256] = "张飞";
    unsigned int age = 38;
    double salary = 20000;
    char buf[1024];
    sprintf (buf, "%s %u %.2lf\n", name, age, salary);
    if (write (fd, buf, strlen (buf) *
        sizeof (buf[0])) == -1) {
        perror ("write");
        return -1;
    }
    struct Employee {
        char name[256];
        unsigned int age;
        double salary;
    }   employee = {"赵云", 25, 8000};
    sprintf (buf, "%s %u %.2lf\n", employee.name,
        employee.age, employee.salary);
    if (write (fd, buf, strlen (buf) *
        sizeof (buf[0])) == -1) {
        perror ("write");
        return -1;
    }
    close (fd);
    if ((fd = open ("text.txt", O_RDONLY)) == -1) {
        perror ("open");
        return -1;
    }
    memset (buf, 0, sizeof (buf));
    if (read (fd, buf, sizeof (buf)) == -1) {
        perror ("read");
        return -1;
    }
    sscanf (buf, "%s%u%lf%s%u%lf", name, &age, &salary,
        employee.name, &employee.age,&employee.salary);
    printf ("姓名:%s\n", name);
    printf ("年龄:%u\n", age);
    printf ("工资:%.2lf\n", salary);
    printf ("员工:%s %u %.2lf\n", employee.name,
        employee.age, employee.salary);
    close (fd);
    return 0;
}

练习:带覆盖检查的文件复制。

代码:copy.c

#include 
#include 
#include 
#include 
#include 
int main (int argc, char* argv[]) {
    if (argc < 3) {
        fprintf (stderr,
            "用法:%s <源文件> <目的文件>\n", argv[0]);
        return -1;
    }
    int src = open (argv[1], O_RDONLY);
    if (src == -1) {
        perror ("打开源文件失败");
        return -1;
    }
    struct stat st;
    if (fstat (src, &st) == -1) {
        perror ("获取源文件属性失败");
        return -1;
    }
    int dst = open (argv[2],
        O_WRONLY | O_CREAT | O_EXCL, st.st_mode);
    if (dst == -1) {
        if (errno != EEXIST) {
            perror ("打开目的文件失败");
            return -1;
        }
        printf ("文件%s已存在,是否覆盖?(Y/N) ",
            argv[2]);
        int ch = getchar ();
        if (ch != 'Y' && ch != 'y')
            return 0;
        if ((dst = open (argv[2],
            O_WRONLY|O_CREAT|O_TRUNC,st.st_mode))==-1){
            perror ("打开目的文件失败");
            return -1;
        }
    }
    unsigned char buf[1024];
    ssize_t bytes;
    while ((bytes = read (src, buf, sizeof (buf))) > 0)
        if (write (dst, buf, bytes) == -1) {
            perror ("写入目的文件失败");
            return -1;
        }
    if (bytes == -1) {
        perror ("读取源文件失败");
        return -1;
    }
    close (dst);
    close (src);
    return 0;
}

九、系统I/O与标准I/O


  1. 当系统调用函数被执行时,需要切换用户态和内核态,
    频繁调用会导致性能损失。

  2. 标准库做了必要的优化,内部维护一个缓冲区,
    只在满足特定条件时才将缓冲区与系统内核同步,
    借此降低执行系统调用的频率,
    减少进程在用户态和内核态之间来回切换的次数,
    提高运行性能。

范例:sysio.c

#include 
#include 
#include 
int main (void) {
    int fd = open ("sysio.dat",
        O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    unsigned int i;
    for (i = 0; i < 100000; ++i)
        write (fd, &i, sizeof (i));
    close (fd);
    return 0;
}

范例stdio.c

#include 
int main (void) {
    FILE* fp = fopen ("stdio.dat", "wb");
    if (! fp) {
        perror ("fopen");
        return -1;
    }
    unsigned int i;
    for (i = 0; i < 100000; ++i)
        fwrite (&i, sizeof (i), 1, fp);
    fclose (fp);
    return 0;
}

结果比较:

# time ./sysio

real    0m17.442s
user    0m0.000s
sys     0m0.284s

# time ./stdio

real    0m0.056s
user    0m0.000s
sys     0m0.009s

十、lseek


  1. 每个打开的文件都有一个与其相关的“文件位置”。

  2. 文件位置通常是一个非负整数,
    用以度量从文件头开始计算的字节数。

  3. 读写操作都从当前文件位置开始,
    并根据所读写的字节数,增加文件位置。

  4. 打开一个文件时,除非指定了O_APPEND,
    否则文件位置一律被设为0。

  5. lseek函数仅将文件位置记录在内核中,
    并不引发任何I/O动作。

  6. 在超越文件尾的文件位置写入数据,
    将在文件中形成空洞。

  7. 文件空洞不占用磁盘空间,但被算在文件大小内。

#include 
#include 

off_t lseek (
    int   fd,     // 文件描述符
    off_t offset, // 偏移量
    int   whence  // 起始位置
);

成功返回当前文件位置,失败返回-1。

whence取值:

SEEK_SET - 从文件头
(文件的第一个字节)。

SEEK_CUR - 从当前位置
(上一次读写的最后一个字节的下一个位置)。

SEEK_END - 从文件尾
(文件的最后一个字节的下一个位置)。

范例:seek.c

#include 
#include 
#include 
#include 
int main (void) {
    int fd = open ("seek.txt", O_RDWR | O_CREAT |
        O_TRUNC, 0664);
    if (fd == -1) {
        perror ("open");
        return -1;
    }
    const char* text = "Hello, World !";
    if (write (fd, text,
        strlen (text) * sizeof (text[0])) == -1) {
        perror ("write");
        return -1;
    }
    if (lseek (fd, -7, SEEK_CUR) == -1) {
        perror ("lseek");
        return -1;
    }
    off_t pos = lseek (fd, 0, SEEK_CUR);
    if (pos == -1) {
        perror ("lseek");
        return -1;
    }
    printf ("当前文件位置:%ld\n", pos);
    text = "Linux";
    if (write (fd, text,
        strlen (text) * sizeof (text[0])) == -1) {
        perror ("write");
        return -1;
    }
    if (lseek (fd, 8, SEEK_END) == -1) {
        perror ("lseek");
        return -1;
    }
    text = "<-这里有个洞洞!";
    if (write (fd, text,
        strlen (text) * sizeof (text[0])) == -1) {
        perror ("write");
        return -1;
    }
    off_t size = lseek (fd, 0, SEEK_END);
    if (size == -1) {
        perror ("lseek");
        return -1;
    }
    printf ("文件大小:%ld字节\n", size);
    close (fd);
    return 0;
}

思考:既然lseek系统调用相当于标C库函数fseek,
那么是否存在与标C库函数ftell相对应的系统调用?

不存在,
因为通过lseek(fd,0,SEEK_CUR)就可以获得当前文件位置。

思考:如何获取文件的大小?

通过lseek(fd,0,SEEK_END)可以获得文件的大小。

十一、打开文件的内核数据结构


通过ls -i可查看文件的i节点号。
i节点记录了文件的属性和数据在磁盘上的存储位置。
目录也是文件,存放路径和i节点号的映射表。

图示:open.bmp
c /c++复习笔记 第三天_第2张图片

范例:bad.c

#include 
#include 
#include 
int main (void) {
    close (1);
    open ("bad.txt", O_WRONLY | O_CREAT |
        O_TRUNC, 0664);
    printf ("Hello, World !\n");
    //perror ("printf");
    //write (1, ...);
    return 0;
}

十二、dup/dup2


#include 

int dup (int oldfd);
int dup2 (int oldfd, int newfd);

成功返回文件描述符oldfd的副本,失败返回-1。

  1. 复制一个已打开的文件描述符。

  2. 返回的一定是当前未被使用的最小文件描述符。

  3. dup2可由第二个参数指定描述符的值。
    若指定描述符已打开,则先关闭之。

  4. 所返回的文件描述符副本,
    与源文件描述符,对应同一个文件表。

图示:dup.bmp
c /c++复习笔记 第三天_第3张图片
范例:dup.c

注意区分通过dup获得的文件描述符副本,
和两次open同一个文件的区别:

dup只复制文件描述符,不复制文件表。

fd1 \

文件表 -> v节点 -> i节点
fd2 /

open创建新文件表,并为其分配新文件描述符。

fd1 -> 文件表1 \

v节点 -> i节点
fd2 -> 文件表2 /

图示:same.bmp
c /c++复习笔记 第三天_第4张图片

范例:same.c

#include 
#include 
#include 
#include 
int main (void) {
    int fd1 = open ("same.txt", O_RDWR | O_CREAT |
        O_TRUNC, 0664);
    if (fd1 == -1) {
        perror ("open");
        return -1;
    }
    printf ("fd1 = %d\n", fd1);
    int fd2 = open ("same.txt", O_RDWR);
    if (fd2 == -1) {
        perror ("open");
        return -1;
    }
    printf ("fd2 = %d\n", fd2);
    const char* text = "Hello, World !";
    if (write (fd1, text,
        strlen (text) * sizeof (text[0])) == -1) {
        perror ("write");
        return -1;
    }
    text = "Linux";
    if (write (fd2, text,
        strlen (text) * sizeof (text[0])) == -1) {
        perror ("write");
        return -1;
    }
    close (fd2);
    close (fd1);
    return 0;
}

你可能感兴趣的:(C,c++,基础知识,c语言)