Linux教程

Linux 操作系统_第一章

本文主要是介绍Linux 操作系统_第一章,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

1.2 嵌入式 Linux 的文件处理

1.2.1 文件描述符及文件处理

1.2.2 Linux 设备文件的打开和创建

1.2.3 Linux 设备文件的读写


1.2 嵌入式 Linux 的文件处理

        在Linux中,文件处理可以分为两类:一类是基于文件描述符的操作,一类是基于数据流的操作。

1.2.1 文件描述符及文件处理

        1.文件描述符

        文件描述符是进程与打开的文件的一个桥梁,通过这个桥梁,才可以在进程中对这个文件进行读写等操作。

        在 Linux 下,每打开一个磁盘文件,都会在内核中建立一个文件表项,文件表项存储着文件的状态信息、存储文件内容的缓冲区和当前文件的读写位置。如果同一个磁盘文件打开了 3 次,就会创建 3 个文件表项(a、b、c),读写该文件时,只会改变文件表项中的文件读写位置,这 3 个文件表项存储在一个文件表数组 table[3] 中,在这里 table[0] = a,table[1] = b,table[2] = c,这个文件表的下标就是文件描述符。将这个文件描述符存放在数组 des[3] = {0,1,2},那么,在进程中就可以通过这个 des 数组下标引用文件表项。也就是说,通过文件描述符就可以访问到磁盘文件。

        2.数据流

        从数据操作方式这个角度来说,Linux 系统中的文件可以看作是数据流。对文件进行操作之前必须先调用标准 I/O 库函数 fopen() 将数据流打开。打开数据流之后,就可以对数据流进行输入输出的操作。

        文件描述符采用最小可用原则,也就是说,当文件描述符 5 被使用,6 未被使用,下一个进程一定是使用 6,而不能使用其它文件描述符。当执行程序时,标准输入、标准输出和标准错误输出(分别占用文件描述符 0、1、2)是自动打开的,也就是说,用户程序只能从文件描述符 3 开始,当不使用时,标准输入、标准输出和标准错误输出也会自动关闭。

1.2.2 Linux 设备文件的打开和创建

        1.设备文件

        Linux 将设备作为文件来操作,对设备的读写操作和对文件的读写操作一样。在访问外部设备时,不需要提供一个标准接口与外部设备相关联,只需要像访问普通文件一样访问设备文件。

        在 Linux 系统中,设备文件或特殊文件时设备驱动程序的接口。这些特殊文件允许应用程序通过标准输入/输出系统调用使用其设备驱动程序与设备进行交互。使用标准系统调用简化了许多编程任务,并且无论设备的特性和功能如何,都可以实现一致的用户空间 I/O 机制。

        设备文件通常提供与标准设备的简单接口,但也可用于访问这些设备上的特定独特资源,因此设备文件存在一个抽象化的设备目录中,关于文件的读写或控制等操作,都可以应用到设备文件上。

        2.设备文件的打开和创建

        基于文件描述符的文件操作函数,都是 Linux 操作系统提供的一组文件操作的接口函数,例如 open()、close()、read()、write()、lseek() 等等。

        要对一个文件进行操作,前提是要这个文件已经存在,然后打开它,操作完成后需要将这个文件关闭,如果不及时关闭,可能会丢失文件中的数据。因此在 Linux 系统中提供了系统调用函数 open() 和 close() 用于打开和关闭一个已经存在的文件。

        (1)open 函数

int open(const char *pathname, int flags);               //	 当文件存在时
int open(const char *pathname, int flags, mode_t mode);  //	 当文件不存在时
int creat(const char *pathname, mode_t mode);
/** 
 * @head   #include <sys/types.h>
 * @head   #include <sys/stat.h>
 * @head   #include <fcntl.h>
 * @brief  打开一个文件
 * @param  pathname:文件的路径及文件名。
 * @param  flags:open 函数的行为标志。
 * @param  mode:文件权限(可读、可写、可执行)的设置。
 * @retval 成功返回打开的文件描述符,失败返回 -1,可以利用 perror 去查看原因。
 */

/**
 * flags 的取值及其含义
 * 	 取值			 含义
 * O_RDONLY     以只读的方式打开
 * O_WRONLY     以只写的方式打开
 * O_RDWR       以可读、可写的方式打开
 * 
 * flags 除了取上述值外,还可与下列值位或
 * 
 * O_CREAT      文件不存在则创建文件,使用此选项时需使用 mode 说明文件的权限
 * O_EXCL       如果同时指定了 O_CREAT,且文件已经存在,则出错
 * O_TRUNC      如果文件存在,则清空文件内容
 * O_APPEND     写文件时,数据添加到文件末尾
 * O_NONBLOCK   当打开的文件是 FIFO、字符文件、块文件时,此选项为非阻塞标志位
 */

/**
 * mode 的取值及其含义
 *   取值  八进制数           含义
 * S_IRWXU  00700   文件所有者的读、写、可执行权限
 * S_IRUSR  00400   文件所有者的读权限
 * S_IWUSR  00200   文件所有者的写权限
 * S_IXUSR  00100   文件所有者的可执行权限
 * S_IRWXG  00070   文件所有者同组用户的读、写、可执行权限
 * S_IRGRP  00040   文件所有者同组用户的读权限
 * S_IWGRP  00020   文件所有者同组用户的写权限
 * S_IXGRP  00010   文件所有者同组用户的可执行权限
 * S_IRWXO  00007   其他组用户的读、写、可执行权限
 * S_IROTH  00004   其他组用户的读权限
 * S_IWOTH  00002   其他组用户的写权限
 * S_IXOTH  00001   其他组用户的可执行权限
 */

        要注意的是,文件权限由 open 函数的 mode 参数和当前进程的 umask 掩码共同决定,umask 掩码是系统的默认值,可以通过在终端下输入命令“umask”查询此值。

        (2)close 函数

int close(int fd);
/**
 * @head   #include <unistd.h>
 * @brief  关闭一个文件
 * @param  fd:调用 open 函数打开文件的文件描述符。
 * @retval 成功返回 0,失败返回 -1,可以利用 perror 查看原因。
 */

        当一个进程终止时,内核对该进程所有未关闭的文件描述符调用 close 函数关闭,所以即使用户程序不调用 close 函数,在终止时内核也会自动关闭它打开的所有文件。但是对于网络服务器这种一直运行的文件,文件描述符需要及时关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。 

        由 open 函数返回的文件描述符一定是该进程尚未使用的最小描述符。第一次调用 open 函数打开文件时,返回的文件描述符通常是 3,再调用返回 4。

        可以利用这一点在标准输入、标准输出和标准错误输出上打开一个新文件,实现重定向功能。

        例如,首先使用 close 函数关闭文件描述符为1的文件,然后调用 open 函数打开一个常规文件,则一定返回文件描述符 1,这时候标准输出就不再是终端了,而是一个常规文件,再调用 printf 就不会打印在屏幕上,而是写到这个文件中。

        基于数据流的文件操作是通过一个 FILE 类型的文件指针实现对文件的访问,在 FILE 结构体类型中存储着很多关于流操作所需的信息,如打开文件的文件描述符、新开辟的缓冲区的指针、缓冲区的大小等等信息。

        基于流的文件操作函数常用的有 fopen()、fclose()、fscanf()、fwrite()、fread()、fgetc() 等。在操作文件之前需要用 fopen()、fclose() 打开或关闭文件。

        (3)fopen 函数

FILE *fopen(const char *path, const char *mode);
/**
 * @head   #include <stdio.h>
 * @brief  用于分配一些资源用于保存该文件的状态信息,用文件描述符来引用这个文件的状态信息。
 * @param  path: 要打开的文件路径名
 * @param  mode: 文件的打开方式
 * @retval 成功,返回值为文件指针,否则返回 NULL,并设置 errno 信息。
 */

/**
 * mode 的取值:
 * r 只读,文件必须存在
 * r+ 读写,文件必须存在
 * w 只写,文件不存在就创建,文件存在就把文件长度截断为0字节后重新写
 * w+ 读写,文件不存在就创建,文件存在就把文件长度截断为0字节后重新写
 * a 只能在文件末尾追加数据,如果文件不存在则创建
 * a+ 允许读写和追加数据,如果文件不存在则创建
 */

        (4)fclose 函数

int fclose(FILE *fp);
/**
 * @head   #include <stdio.h>
 * @brief  关闭文件,释放文件在操作系统中占用的资源,使文件描述符无效。
 * @param  fp: 要关闭文件的文件描述符
 * @retval 函数调用成功,返回值为0,否则返回 EOF 并设置 errno 信息。
 */

/**
 * EOF 的定义形式
 * #ifndef EOF
 * #define EOF (-1)
 * #endif
 */

1.2.3 Linux 设备文件的读写

        1.基于文件描述符的文件读写操作函数

        (1)read 函数

ssize_t read(int fd, void *addr, size_t count);
/**
 * @head   #include <unistd.h>
 * @brief  把指定数目的数据读到内存
 * @param  fd:文件描述符。
 * @param  addr:数据首地址。
 * @param  count:读取字节的个数。
 * @retval 成功返回实际读取到的字节个数,失败返回 -1,可以利用 perror 去查看原因。
 */

        读取文件的数据时,文件的当前读写位置会向后移。需要注意的是:这个读写位置和使用 C 标准 I/O 库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用 C 标准 I/O 库时的读写位置是用户空间 I/O 缓冲区中的位置。

        以下几种情况,返回的字节数会小于 count 值:

  • 读常规文件时,在读到 count 个字节之前已达到文件末尾。例如,距文件末尾还有30个字节数而请求读到100个字节,则 read 返回 30,下次 read 返回 0;
  • 从终端设备上读时,通常以行为单位,读到换行符就返回;
  • 从网络上读时,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数。

        (2)write() 函数

ssize_t write(int fd, const void *addr, size_t count);
/**
 * @head   #include <unistd.h>
 * @brief  把指定数目的数据写到文件
 * @param  fd:文件描述符。
 * @param  addr:数据首地址。
 * @param  count:写入数据的字节个数。
 * @retval 成功返回实际写入数据的字节个数,失败返回 -1,可以利用 perror 去查看原因。
 */

        当向常规文件写入数据时,返回值会是字节数 count,但向终端设备或者网络中写入数据时,返回值不一定为写入的字节数。

        2.基于数据流的文件读写操作函数

        基于数据流的字符输入输出操作,实际上就是以字节为单位的读写操作,在 C 标准库中常用的读写字符的函数是 fgetc() 和 fputc()。

        (1)fgetc() 函数

int fgetc(FILE *stream);
/**
 * @head   #include <stdio.h>
 * @brief  从 stream 中读取一个字节
 * @param  stream: FILE 结构体类型的指针,用于指向一个文件
 * @retval 成功,返回读到的字节,如果出错或者读到文件末尾时,返回 EOF。
 */
   在程序中,偶尔会遇到 getchar 函数,也是用于读取一个字节,但它是从标准输入读一个字节。在程序调用 getchar 函数相当于调用 fgetc(stdin)。

在使用 fgetc 函数时需要注意以下两点:

  • 调用 fgetc 函数时,指定的文件的打开方式必须是可读的;

  • 函数 fgetc 调用成功时,返回的是读到的字节,应该为 unsigned char 类型,但 fgetc() 函数原型中返回值的类型是 int,原因在于函数调用出错或读到文件末尾时 fgetc() 函数会返回 EOF,即 -1,保存在 int 型的返回值中是 0xffffffff,如果读到字节 0xff,由 unsigned char 型转换为 int 型是 0x000000ff,只有规定返回值是 int 型才能把两种情况区分开,如果规定返回值是 unsigned char 型,那么返回值是 0xff 时无法区分到底是 EOF 还是字节 0xff。

        (2)fputc 函数

int fputc(int c, FILE *stream);
/**
 * @head   #include <stdio.h>
 * @brief  主要用于向指定的文件写一个字节
 * @param  c: 写入字节
 * @param  stream: FILE 结构体类型的指针,用于指向一个文件
 * @retval 成功,返回写入的字节,否则,返回 EOF。
 */

        在程序中,偶尔会遇到 putchar 函数,也是用于向文件中写入一个字节,但它是为向标准输出写一个字节。在程序调用 putchar 函数相当于调用 fputc(c, stdin)。

        在使用 fputc() 函数时需要注意以下两点:

  • 调用 fputc() 函数时,指定的文件的打开方式必须是可写(包括追加)的。

  • 每写入一个字符,文件内部位置指针向后移动一个字节。

        C 标准库函数为字符串的输入输出提供了 fputs 函数和 fgets 函数。fputs 函数与 fputc 函数类似,不同的是 fputc 每次只向文件中写一个字符,而 fputs 函数每次向文件中写入一个字符串。fgets 函数和 fgetc 函数之间的关系是读取字符串与读取字符的关系。

        (3)fgets() 函数

char *fgets(char *s, int size, FILE *stream);
/**
 * @head   #include <stdio.h>
 * @brief  从 stream 所指向的文件中读取一串小于 size 所表示的字节数的字符串,然后将字符串存储到 s 所指向的缓冲区。
 * @param  s: 指向一个缓冲区
 * @param  size: 读取字符串的最大值 - 1
 * @param  stream: FILE 结构体类型的指针,用于指向一个文件
 * @retval 成功时返回内容为返回指针 s 所指向的缓冲区部分的指针,函数调用出错或者读到文件末尾时返回 NULL。
 */

        在调用 fgets 函数读取字符串时,以读取到‘\n’转义字符为结束,并在该行末尾添加一个‘\0’组成完整的字符串。在 size 字节范围内没有读到‘\n’结束符,则添加一个‘\0’,组成字符串缓存到缓冲区,文件中剩余的字符,等待下一次调用 fgets 函数时再读取。

        对于 fgets 而言,‘\n’是一个特别的字符,作为结束符,而‘\0’并无特别之处,只用作普通字符读入。正因为‘\0’作为一个普通的字符,因此无法判断缓冲区中的‘\0’究竟是从文件读上来的字符还是由 fgets 函数自动添加的结束符,所以 fgets 函数只用于读文本文件而不提倡读二进制文件,并且文本文件的所有字符不能有‘\0’。

        (4)fputs 函数

int fputs(const char *s, FILE *stream);
/**
 * @head   #include <stdio.h>
 * @brief  向 stream 指针指向的文件写入 s 缓冲区的字符串
 * @param  s: 指向一个缓冲区
 * @param  stream: FILE 结构体类型的指针,用于指向一个文件
 * @retval 函数返回为一个非负整数;否则返回EOF。
 */
    缓冲区 s 中保存的是以‘\0’结尾的字符串,fputs 将该字符串写入文件 stream,但并不写入结尾的‘\0’,且字符串中可以有‘\n’也可以没有。
这篇关于Linux 操作系统_第一章的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!