IO与操作系统
IO的概念
I/O,即输入/输出(Input/Output),是计算机科学中的一个基本概念,涉及数据在不同实体之间的传输。
基本概念:
- 输入:指数据进入系统或程序的过程,例如用户通过键盘输入数据,或程序从文件或网络接收数据。
- 输出:指数据离开系统或程序的过程,例如程序将结果打印到屏幕或写入文件。
I/O设备:
- 输入设备:如键盘、鼠标、扫描仪、触摸屏、麦克风等,用于向计算机提供输入。
- 输出设备:如显示器、打印机、扬声器等,用于展示计算机处理后的数据。
I/O操作:
- 读取:从存储设备或输入设备中获取数据。
- 写入:将数据发送到存储设备或输出设备。
I/O流:
- 数据流可以是字节流或字符流,根据处理的数据单位和方式不同而有所区别。
I/O模式:
- 阻塞I/O:操作必须等待I/O请求完成才能继续执行。
- 非阻塞I/O:操作不需要等待I/O请求完成,可以继续执行其他任务。
- 同步I/O:I/O操作由请求它的同一线程执行。
- 异步I/O:I/O操作由操作系统或其他线程执行,完成后通知请求者。
I/O缓冲:
- 许多I/O系统使用缓冲区来提高效率,数据先写入缓冲区,再一次性传输到目标设备。
I/O接口:
- 硬件I/O接口:硬件层面上连接输入/输出设备与计算机的接口,如USB、HDMI等。
- 软件I/O接口:软件层面上用于执行I/O操作的API或系统调用。
I/O在操作系统中的作用:
- I/O是操作系统核心功能之一,负责管理计算机硬件设备和软件之间的数据交换。
I/O是计算机系统中不可或缺的一部分,无论是在硬件层面、软件层面还是用户交互层面,都扮演着重要角色。理解I/O的概念对于学习计算机科学和开发高效的应用程序至关重要
操作系统的概念
操作系统(Operating System, OS)是管理计算机硬件资源和提供各种服务的系统软件,它是计算机系统中最基本的软件
操作系统通常包含两种不同的含义
(1)指完整的软件包:包括核心软件与应用软件
应用软件:命令解释器、图形用户界面、文件操作工具与文件编辑器
核心软件:管理和分配计算机资源(CPU,RAM,其它设备),即操作系统核心软件[内核]
(2)专指操作系统核心软件
内核的职责:
1. 系统管理和资源分配:
- 操作系统负责管理系统资源,包括CPU时间、内存、存储设备、输入/输出设备等。
2. 用户接口:
- 操作系统提供了用户与计算机交互的接口,如命令行界面(CLI)和图形用户界面(GUI)。
3. 程序执行:
- 操作系统负责程序的加载、执行和调度。
4. 进程管理:
- 操作系统管理进程(程序的执行实例)的生命周期,包括创建、调度、同步和通信。
5. 线程管理:
- 在支持多线程的操作系统中,操作系统还负责管理线程的创建、调度和同步。
6. 内存管理:
- 操作系统负责内存的分配和回收,包括虚拟内存管理、内存保护和地址转换。
7. 文件系统:
- 操作系统通过文件系统提供数据的持久存储,管理文件和目录的创建、删除、读写和组织。
8. I/O设备管理:
- 操作系统管理输入/输出设备,提供设备驱动程序以实现硬件的抽象和控制。
9.系统调用:
- 操作系统提供了一组系统调用,允许应用程序请求操作系统的服务。
10. 系统启动和关闭:
- 操作系统管理计算机的启动和关闭过程,包括硬件检测、系统初始化和清理。
11. 网络通信:
- 操作系统支持网络协议,提供网络通信和数据传输的功能。
12. 安全性:
- 操作系统提供安全机制,如用户认证、权限控制和数据保护。
13. 错误检测和处理:
- 操作系统负责检测和处理错误,如硬件故障、程序错误等。
14. 系统维护:
- 操作系统提供了维护工具,如性能监控、资源管理、软件更新等。
15. 可移植性:
- 操作系统的设计允许在不同的硬件平台上运行,提供了一定的可移植性。
Linux操作系统结构
Linux操作系统是一种基于Unix的开源操作系统内核
Linux操作系统结构关键组件:
1. 内核(Kernel)
- 内核是Linux系统的核心,负责管理系统资源,如CPU、内存、I/O设备等。
- 内核提供了硬件抽象层,为应用程序提供统一的接口。
2. 用户空间(User Space)
- 用户空间是操作系统中除内核外的所有部分,包括应用程序、库、服务等。
- 用户空间的程序通过系统调用与内核交互。
3. 系统调用(System Calls)
- 系统调用是用户空间程序请求内核服务的接口。
- 常见的系统调用包括文件操作、进程控制、内存管理等。
4. C库(C Library)
- C库,如glibc,提供了对系统调用的封装,使得程序员可以使用C语言编写的高级函数来执行底层操作。
glibc 是属于 GNU(GNU’s Not unix) 工程的一部分,这个工程当初的目标是为了开发一款完整的操作系统,但在开发过程中将除了 Linux 内核以外的组件都开发完成,由于难度很大,开发周期长,在 1992 年 由 Linus Torvalds 开发出来了 Linux 内核,填补了 GNU 系统的一个重要空白,所有后面将 GNU 组件与 Linux 合并组成现在的 GNU/Linux
glibc 包含 标准 c 库函数集合 和 系统调用
标准的 c 库函数是跨平台的,既可以在 Linux 系统下调用,也可以在 windows 系统下调用
系统调用是 Linux 内核给用户提供的访问接口,但在 glibc 中封装了系统调用接口而形成了 glibc 的库函数.
glibc 库函数主要是封装了系统调用的过程, 相应的系统调用一般实现在 Linux 内核中一般的 glibc 中的库函数都会与系统调用关联,但也有库函数不需要使用系统调用,比如字符串操作函数
一般分为用户层和内核层
用户层:表示在内核层之上的库与应用程序(app)
内核层:操作系统内核
用户层与内核层是相辅相成,用户层的应用程序依赖于库或内核,库与内核给应用层提供服务内核通过系统调用来给应用层提供接口
Linux IO设计思想
Linux IO框架基于一切皆文件的思想来设计
目的 : 屏蔽底层不同设备之间的 IO 差异,给应用层提供统一的操作接口
思想 : 即将底层的 IO 操作统一抽象成文件操作,操作系统只需要提供一组文件 IO 操作接口就可以为应用程序提供 IO 服务
文件IO操作
文件描述符(File Descriptors)
- 在Linux中,所有I/O操作都通过文件描述符进行,它是一个代表打开文件的非负整数。
- 标准文件描述符包括:
stdin
(0),stdout
(1),stderr
(2)。
open函数
open
函数是 POSIX 标准和 Linux 系统中用于打开文件的基本函数。它用于创建或打开一个文件,并返回一个文件描述符,该文件描述符可以用于后续的读写操作
函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, ...);
参数说明:
- pathname:指向包含文件名的字符串的指针。
- flags:文件打开方式的标志位,可以是以下值之一或它们的组合:
O_RDONLY
:以只读方式打开文件。O_WRONLY
:以只写方式打开文件。O_RDWR
:以读写方式打开文件。O_CREAT
:如果文件不存在,创建文件。如果O_CREAT
标志被设置,并且文件不存在,open
将创建一个新文件,并可能需要第三个参数来设置文件的权限模式。O_EXCL
:与O_CREAT
一起使用,文件必须不存在。O_TRUNC
:如果文件存在,截断文件内容到长度为0。O_APPEND
:每次写操作都从文件末尾开始。
- ...:可选参数,用于指定文件的权限模式,仅当
O_CREAT
标志被设置时使用。
返回值:
- 成功时,
open
返回非负的文件描述符。 - 失败时,返回
-1
并设置errno
以指示错误。
close函数
close
函数是 POSIX 标准和 Linux 系统中用于关闭文件描述符的基本函数。当一个文件描述符不再需要时,使用 close
函数可以释放与该文件描述符关联的所有资源
函数原型
#include <unistd.h>
int close(int fd);
参数说明:
fd
:文件描述符(file descriptor),一个非负整数,表示要关闭的文件或设备。
返回值:
- 成功时,
close
返回 0。 - 失败时,返回 -1 并设置
errno
以指示错误
示例代码
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("example.txt", O_RDWR);
if (fd == -1) {
perror("Failed to open file");
return 1;
}
// 执行文件操作...
if (close(fd) == -1) {
perror("Failed to close file");
return 1;
}
return 0;
}
read函数
read
函数是 POSIX 标准和 Linux 系统中用于从文件描述符中读取数据的基本函数。它允许你从指定的文件描述符中读取一定数量的数据。
函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数说明:
fd
:文件描述符,一个非负整数,表示要读取数据的文件或设备。buf
:指向缓冲区的指针,数据将被读取到这个缓冲区中。count
:要读取的字节数。
返回值:
- 成功时,
read
返回实际读取的字节数,如果达到 EOF(文件末尾),返回 0。 - 失败时,返回 -1 并设置
errno
以指示错误。
示例代码:
#include <unistd.h>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Failed to open file");
return 1;
}
char buffer[1024];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead == -1) {
perror("Failed to read from file");
close(fd);
return 1;
}
if (bytesRead == 0) {
printf("End of file reached.\n");
} else {
// 处理读取的数据
printf("Read %ld bytes from file.\n", bytesRead);
}
close(fd); // 关闭文件描述符
return 0;
}
write函数
write
函数是 POSIX 标准和 Linux 系统中用于向文件描述符写入数据的基本函数。它允许你将数据从缓冲区写入到指定的文件或设备中
函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数说明:
fd
:文件描述符,一个非负整数,表示要写入数据的文件或设备。buf
:指向包含要写入数据的缓冲区的指针。count
:要写入的字节数。
返回值:
- 成功时,
write
返回实际写入的字节数。 - 失败时,返回 -1 并设置
errno
以指示错误
示例代码
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("Failed to open file");
return 1;
}
const char *message = "Hello, World!\n";
ssize_t bytesWritten = write(fd, message, strlen(message));
if (bytesWritten == -1) {
perror("Failed to write to file");
close(fd);
return 1;
}
printf("Wrote %ld bytes to file.\n", bytesWritten);
close(fd); // 关闭文件描述符
return 0;
}
lseek函数
lseek
函数是 POSIX 标准和 Linux 系统中用于改变文件描述符的文件位置指针的函数。使用 lseek
,你可以查询或修改文件描述符指向的文件中的当前读写位置
函数原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数说明:
fd
:文件描述符,一个非负整数,表示要改变位置指针的文件。offset
:要移动的字节偏移量。正偏移量表示向前移动,负偏移量表示向后移动。whence
:指定offset
参数的起始位置,可以是以下宏之一:SEEK_SET
:文件的开头(这是默认的位置)。SEEK_CUR
:当前文件位置。SEEK_END
:文件的结尾。
返回值:
- 成功时,
lseek
返回新的文件位置偏移量,从文件开头开始计算。 - 失败时,返回
(off_t)-1
并设置errno
以指示错误
示例代码
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Failed to open file");
return 1;
}
// 移动到文件开头的第 10 个字节
off_t new_pos = lseek(fd, 10, SEEK_SET);
if (new_pos == (off_t)-1) {
perror("Failed to seek");
close(fd);
return 1;
}
// 读取当前位置的 20 个字节
char buffer[20];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead == -1) {
perror("Failed to read from file");
}
close(fd); // 关闭文件描述符
return 0;
}