Linux系统编程-标准IO&系统调用IO

目录

标准IO

什么是标准IO

什么是缓冲区

缓冲区的概念

缓冲区的实现

缓冲区的类型

标准IO函数

1.fopen

2.fdopen

3.fclose

4.fgetc

5.fputc

6.fgets

7. fputs

8.fread

9.fprintf

10.fscanf

11.fwrite

12.fseek 

13.ftell

14.fflush

15.rewind

16.perror

 17.strerror

18.tempnam

19.tmpfile

系统调用IO

什么是系统调用IO/文件IO

系统调用IO函数

1.open

2.read

3.write

4.lseek

5.truncate&ftruncate

6.dup&dup2

SYNC

FCNTL

IOCTL

利用IO自写getline函数


标准IO

什么是标准IO

        Stdio 标准输入输出以FILE结构体类型,通过标准库函数(如 fread、fwrite、fscanf、fprintf 等)进行操作。这些函数通常在内部使用缓冲机制,以减少对系统调用的依赖。
        标准 IO 库在进程的用户态的虚拟内存维护了一个缓冲区,数据先写入缓冲区,然后再批量写入文件系统,因此在标准IO和系统IO同时执行时,系统IO更快,因为系统IO直接和内核交互。


什么是缓冲区

        缓冲区是一种数据存储机制,用于在数据传输或处理过程中暂存数据,主要作用是合并系统调用,可以通过setbuf更改。

缓冲区的概念

        暂存数据:缓冲区用于暂存即将被处理或传输的数据,以减少数据传输过程中的延迟和等待时间。
        平滑数据流:通过缓冲区,可以将数据流从生产者平滑地传递给消费者,避免因速度不匹配而导致的数据丢失或处理延迟。
        提高效率:缓冲区可以减少对硬件设备的直接访问次数,通过批量处理数据来提高数据传输和处理的效率。


缓冲区的实现


        内存:缓冲区通常实现为内存中的一段连续空间。这可以是物理内存,也可以是虚拟内存。
硬件设备:
        硬盘:硬盘控制器通常包含缓冲区,用于暂存读写操作的数据。
        网络设备:网络接口卡(NIC)包含缓冲区,用于暂存网络数据包。
        图形卡:图形处理单元(GPU)包含缓冲区,用于暂存图形渲染数据。
        专用硬件:某些系统可能使用专用硬件(如FPGA或ASIC)来实现缓冲区,以提供更高的性能和更低的延迟。
        操作系统和驱动程序:操作系统和设备驱动程序负责管理缓冲区的分配和使用。驱动程序通常会根据硬件设备的特性和需求来优化缓冲区的使用。


缓冲区的类型

行缓冲:换行和缓冲去满时刷新(标准输出)
全缓冲:缓冲区满的时候刷新(默认,只要不是终端设备)
无缓冲:如stderr,需要立即输出的内容


标准IO函数

1.fopen

FILE *fopen(const char *pathname, const char *mode);

        const表示参数不会被修改,返回FILE指针(存储在堆上)或NULL 模式r和r+要求文件必须存在
        w,w+,a,a+创建的文件权限是 0666 & ~umask 结果是 664,umask一般是0002,目的是为了防止产生文件权限过松

2.fdopen

FILE *fdopen(int fd, const char *mode);

        把已有的文件描述符通过指定的方式,封装成一个FILE流

3.fclose

int fclose(FILE *stream);

        关闭一个FILE流,成功返回0,失败返回EOF或者error

4.fgetc

int fgetc(FILE *stream);

        返回读取的字符,将其作为无符号字符强制转换为整数,或者在文件结束或出错时返回EOF

5.fputc

int fputc(int c, FILE *stream);

        字符放入流,返回值同fgetc

6.fgets

char *fgets(char *s, int size, FILE *stream);

        读取到size-1或者'\n'停止,成功返回s,失败返回NULL或者error

7. fputs

int fputs(const char *s, FILE *stream);

        const常量s写入流

8.fread

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

        size是读取字节数,nmemb是读取对象数,没有边界检查所以读取字节数不能大于实际字符数,返回成功读到对象个数

9.fprintf

int fprintf(FILE *stream, const char *format, ...);

        指定格式输出到流(stdin,stdout,strerr等),返回值同printf

10.fscanf

int fscanf(FILE *stream, const char *format, ...);

        从流中按指定格式读取,返回值同scanf

11.fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

        返回成功写入c对象个数

12.fseek 

int fseek(FILE *stream, long offset, int whence);

        offset偏移大小,whence偏移参考系(SEEK_SET, SEEK_CUR, SEEK_END),作用是文件指针移动到参考系+—偏移的位置,返回偏移offset

13.ftell

long ftell(FILE *stream);

        返回文件指针现在位置(对于文件首的偏移字节数)

14.fflush

int fflush(FILE *stream);

        强制刷新流的缓冲区,如果参数为空那么刷新所有打开的流

15.rewind

void rewind(FILE *stream);

        FILE流定位到首位置

16.perror

void perror(const char *s);

        向stderr输出字符串s和error信息

 17.strerror

char *strerror(int errnum);

        输入errorno输出对应的error信息

18.tempnam

char *tempnam(cahr *s);

        生成一个唯一的临时文件名,成功时,返回一个指针,指向一个字符串包含了生成的临时文件名。失败时,返回 NULL,并设置 errno 以指示错误类型。在并发下不安全

19.tmpfile

FILE *tmpfile(void);

        创建并以二进制打开一个临时文件,返回匿名的FILE流


系统调用IO

什么是系统调用IO/文件IO

        系统调用IO减少了缓冲机制的间接层,每次调用 write 或 read 都会直接与内核交互,但是每次系统调用都需要从用户态切换到内核态,这涉及到上下文切换,可能会有一定的开销。
        标准 IO 的缓冲机制可以减少这种切换的次数,但代价是增加了内存的使用和可能的延迟。
        fd文件描述符是文件IO中贯穿始终的类型,本质是一个整形数(数组的下标),而文件描述符表中存放的是指针,指针指向文件信息结构体(包含pos,count,inode等需要的信息)
        fd数组中0位是stdin,1位stdout,2位stderr,因此存储fd从第三位开始(优先使用最小的),文件表述符表又称fd数组是内核的一部分,每个新进程在创建时,操作系统内核会为其在内核空间中初始化一个独立的文件描述符表。进程通过系统调用在用户空间操作这些文件描述符。


系统调用IO函数

1.open

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

        当flags有O_CREAT时,用三参open,mode通常使用 S_IRUSR(用户可读)、S_IWUSR(用户可写)、S_IXUSR(用户可执行)等宏定义来设置权限。返回值是创建的文件描述符fd

2.read

ssize_t read(int fd, void *buf, size_t count);

        读一个fd到buf, 返回读到的字节数,失败返回-1

3.write

ssize_t write(int fd, const void *buf, size_t count);

        从const buf写入fd, 返回写入的字节数,失败返回-1

4.lseek

off_t lseek(int fd, off_t offset, int whence);

        同fseek,对于某些类型的文件(如管道或终端),lseek 可能不会产生预期的结果,因为这些文件类型可能不支持随机访问。

5.truncate&ftruncate

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

        如果文件原来的长度大于 length 参数指定的大小,文件将被截断到这个新的大小。如果文件原来的长度小于 length,文件将被扩展,并且新的空间将被填充为零。

6.dup&dup2

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

        fd重定向,dup是生成fd副本定向到最小空闲fd,dup2是生成oldfd副本,关闭newfd并且放入副本,返回值是fd


SYNC

        sync 为每个文件描述符(FD)维护一个内核缓冲区。

        sync是一个系统调用,也是一个命令行工具,用于将系统缓冲区内的所有未写入数据强制写入到磁盘上。这通常用于确保在系统崩溃或重启之前,所有的数据都已经安全存储。

        作为系统调用:sync 没有参数,调用后会刷新所有文件系统缓冲区。
        作为命令行工具:在终端中运行 sync 命令也会达到同样的效果。

int fsync(int fd);

        fd表示要同步的文件。系统调用,用于确保指定fd对应的文件数据被同步到磁盘上

int fdatasync(int fd);

        fdatasync 是 fsync 的一个变体,它只同步文件的数据部分,而不同步元数据(如访问时间、修改时间等)。这可以提供比 fsync 更快的同步速度,因为元数据的更新通常不需要磁盘I/O操作。


FCNTL

        fcntl是一个非常强大的系统调用,可以用于实现多种文件相关的控制操作.

int fcntl(int fd, int cmd, ... /* arg */ );

参数说明:
        -fd:文件描述符,表示要操作的文件。
        -cmd:命令,指定了要执行的操作类型。
        -arg(可选):一些命令可能需要额外的参数,这些参数会通过这个参数传递。

常用命令(cmd):
        -F_DUPFD:复制文件描述符,返回给定文件描述符的新文件描述符,从指定位置开始计数。
        -F_GETFD:获取文件描述符的标志。
        -F_SETFD:设置文件描述符的标志,如 FD_CLOEXEC。
        -F_GETFL:获取文件状态标志,如 O_NONBLOCK。
        -F_SETFL:设置文件状态标志。
        -F_GETLK:获取记录锁信息。
        -F_SETLK:设置记录锁。
        -F_SETLKW:与 F_SETLK 类似,但如果锁已经被占用,则等待直到锁被释放。
        -F_GETOWN:获取接收 SIGURG 信号和 out-of-band 数据的进程或线程的 ID。
        -F_SETOWN:设置接收上述信号的进程或线程的 ID。

返回值:
        -成功时,大多数 fcntl 命令返回一个非负值,具体值取决于命令。
        -失败时,返回 -1,并设置 errno 以指示错误类型。


IOCTL

        允许用户空间的程序执行或请求设备驱动程序执行非标准的操作。通常用于执行那些标准系统调用(如 read、write)无法实现的设备特定操作,例如:
                    -配置设备参数(如波特率、停止位等)。
                    -检索设备状态。
                    -执行设备特定的命令(如发送一个硬件重置信号)。
        ioctl 的强大之处在于它的灵活性,它可以接受几乎任何类型的数据作为输入或输出,这使得它可以用于各种不同的设备和协议。

int ioctl(int fd, unsigned long request, ... /* void *arg */ );

参数说明:
        -fd:文件描述符,表示要操作的设备或文件。
        -request:指定了要执行的控制命令。
        -arg(可选):一个可选参数,根据 request 的不同,可能需要传递一个指针来接收或发送数据。

返回值:
        -成功时,返回非负值,具体值取决于操作的类型。
        -失败时,返回 -1,并设置 errno 以指示错误类型。


利用IO自写getline函数

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>

ssize_t K_getline(char **lineptr, size_t *n, FILE *stream) {
    long int pos, len;

    // 保存当前文件位置
    pos = ftell(stream);
    if (pos < 0) return -1; // 如果ftell失败,返回错误

    // 移动到文件末尾
    fseek(stream, 0, SEEK_END);

    // 获取文件长度
    len = ftell(stream) - pos;

    // 恢复到原始位置
    fseek(stream, pos, SEEK_SET);

    // 为lineptr分配内存
    if (*lineptr == NULL || *n < (size_t)len + 1) {
        free(*lineptr); // 释放旧内存
        *lineptr = malloc((len + 1) * sizeof(char)); // 分配新内存,包含'\0'位
        if (*lineptr == NULL) return -1; // 如果内存分配失败,返回错误
        *n = len + 1;
    }

    // 读取一行
    if (fgets(*lineptr, (size_t)len + 1, stream) == NULL) return -1; // 如果读取失败,返回错误

    // 返回读取的行的长度,不包括最后的空字符
    return strlen(*lineptr);
}
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值