UNIX-Linux环境编程(四):文件系统(上)

一、系统调用

系统调用
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命令:跟踪系统调用

二、一切皆文件

1、Linux环境中的文件具有特别重要的意义,因为它为操作系统服务和设备,提供了一个简单而统一的接口,在Linux中,(几乎)一切皆文件
2、程序完全可以象访问普通磁盘文件一样,访问串行口、网络、打印机或其它设备。
3、大多数情况下只需要使用五个基本系统调用 open/close/read/write/ioctl,即可实现对各种设备的输入和输出。
4、Linux中的任何对象都可以被视为某种特定类型的文件,可以访问文件的方式访问之。
5、广义的文件

  1. 目录文件
  2. 设备文件
    A. 控制台:/dev/console
    B. 声卡:/dev/audio
    C. 标准输入输出:/dev/tty
    D. 空设备:/dev/null
  3. 普通文件

三、文件相关系统调用

1.open 	  - 打开/创建文件
2.creat  	- 创建空文件
3.close  	- 关闭文件
4.read   	- 读取文件
5.write  	- 写入文件
6.lseek  	- 设置读写位置
7.fcntl  	- 修改文件属性
8.unlink 	- 删除硬链接
9.rmdir  	- 删除空目录
10.remove   - 删除硬链接(unlink)或空目录(rmdir)

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

四、文件描述符

1、非负的整数。
2、表示一个打开的文件。
3、由系统调用(open)返回,被内核空间(后续系统调用)引用。
4、内核缺省为每个进程打开三个文件描述符:
•stdin 0 - 标准输入
•stdout 1 - 标准输出
•stderr 2 - 标准出错
在unistd.h中被定义为如下三个宏:
1.#define STDIN_FILENO 0
2.#define STDOUT_FILENO 1
3.#define STDERR_FILENO 2

范例:redir.c

#include <stdio.h>

int main (void) 
{
    int data;
    fscanf (stdin, "%d", &data);
    fprintf (stdout, "标准输出:%d\n", data);
    fprintf (stderr, "标准错误:%d\n", data);
    return 0;
}

五、open/creat/close

#include <fcntl.h>

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 <unistd.h>

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

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

范例:open.c

#include <stdio.h>
#include <fcntl.h>

int main () 
{
    int fd1 = open ("open.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (fd1 == -1)
    {
        perror ("open");
        return -1;
    }

    printf ("fd1 = %d\n", 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 <unistd.h>

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

成功返回实际写入的字节数,失败返回-1。

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

范例:write.c

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main () 
{
    int fd = open ("write.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    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 <unistd.h>

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

	成功返回实际读取的字节数,失败返回-1。

范例:==read.c==
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main () 
{
    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\n", text);

    close (fd);

    return 0;
}

二进制读写和文本读写

范例:binary.ctext.c
binary.c

#include <stdio.h>
#include <fcntl.h>

int main () 
{
    int fd = open ("binary.dat", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    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 (salary)) == -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 <stdio.h>
#include <string.h>
#include <fcntl.h>

int main () 
{
    int fd = open ("text.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    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", 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;
}

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

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

九、lseek

1、每个打开的文件都有一个与其相关的“文件位置”。
2、文件位置通常是一个非负整数,用以度量从文件头开始计算的字节数。
3、读写操作都从当前文件位置开始,并根据所读写的字节数,增加文件位置。
4、打开一个文件时,除非指定了O_APPEND,否则文件位置一律被设为0。
5、lseek函数仅将文件位置记录在内核中,并不引发任何I/O动作。
6、在超越文件尾的文件位置写入数据,将在文件中形成空洞
7、文件空洞不占用磁盘空间,但被算在文件大小内

#include <sys/types.h>
#include <unistd.h>

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

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

whence取值:

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

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

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

范例:seek.c

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) 
{
    int fd = open ("seek.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    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 ("当前文件位置:%d\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 ("文件大小:%d字节\n", size);

    close (fd);

    return 0;
}

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

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

十一、dup/dup2

#include <unistd.h>

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

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

1、复制一个已打开的文件描述符。
2、返回的一定是当前未被使用的最小文件描述符。
3、dup2可由第二个参数指定描述符的值,若指定描述符已打开,则先关闭之
4、所返回的文件描述符副本,与源文件描述符,对应同一个文件表。

dup

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

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

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

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

open
范例:same.c

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main () 
{
    int fd1 = open ("same.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    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);

    int fd3 = open ("same.txt", O_RDWR);
    if (fd3 == -1) 
    {
        perror ("open");
        return -1;
    }

    printf ("fd3 = %d\n", fd3);

    const char* text = "Hello, World !";
    if (write (fd1, text, strlen (text) * sizeof (text[0])) == -1)
    {
        perror ("write");
        return -1;
    }

    if (lseek (fd2, -7, SEEK_END) == -1) 
    {
        perror ("lseek");
        return -1;
    }

    text = "Linux";
    if (write (fd3, text, strlen (text) * sizeof (text[0])) == -1) 
    {
        perror ("write");
        return -1;
    }

    close (fd3);
    close (fd2);
    close (fd1);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/perror_0/article/details/106882530