Linux 操作系统_第一章
2021/10/14 7:16:51
本文主要是介绍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 操作系统_第一章的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法