1、linux读写底层原理

以最底层的系统调用read和write为例,来讲解。

read从原理上理解就是把对应文件描述符在内核缓存中的值复制到进程空间中

write则是把一系列值从进程空间中复制到对应的文件描述符的内核缓冲区中

有两个概念需要明确:

内核缓冲区:这是系统内核自己设置的缓冲区,不管使用什么IO函数,都是存在的,read和write也有内核缓冲区

进程缓冲区:这个缓冲区不一定存在,比如直接使用read函数或者write,那么就没有进程缓冲区。而这个缓冲区一般是人为设定的,比如CSAPP第十章中的rio_read()函数就是自己编写了一个进程缓冲区。又比如muduo中的Buffer也是进程缓冲区

之所以要使用进程缓冲区,是因为每次从内核缓冲区复制数据到进程空间,都需要陷入内核调用,非常浪费资源,所以就一次性把内核缓冲区的内容尽可能多的复制到进程缓存区中,这样避免资源浪费。

2、linux读写IO函数分类

总共分为三类:文件读写IO,标准IO以及网络高级IO

①文件读写IO是最底层的IO函数不带进程缓存,主要的函数如下:

#include <fcntl.h>
#include <unistd.h>
int open(const char *pathname, int oflag, ... mode_t mode);
#成功返回文件描述符, 失败返回-1
int close(int filedes);
#成功返回0, 失败返回-1
off_t lseek(int filedes, off_t offset, int whence);
#成功返回新的文件偏移量,出错返回-1
ssize_t read(int filedes, void *buf, size_t nbytes);
#成功则返回读取到的字节数,若已到文件的结尾返回0,出错返回-1
ssize_t write(int filedes, void *buf, size_t nbytes);
#成功则返回写入的字节数,出错返回-1

②标准IO是C建立的一个标准I/O函数库,具有可移植性。因为C也针对windows封装一套函数接口一样的函数。并且标准IO是带有进程缓存的。主要函数如下:

#include <stdio.h>
#fopen 打开一个指定的文件
FILE *fopen(const char *restrict pathname, const char *restrict type);
#freopen 在一个指定的流上打开一个文件,比如在标准输出流上打开某文件
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
#dopen 打开指定的文件描述符代表的文件。常用于读取管道或者其他特殊类型的文件,因为这些文件不能直接用fopen打开。
FILE *dopen(int filedes, const char *type);
# 成功返回FILE类型指针,出错返回NULL
# type 参数指定操作类型,入读写,追加等等。

关闭
#include <stdio.h>
int flose(FILE *fp);
# 成功返回0,出错返回EOF
每次一个字符的io
#include <stdio.h>

每次一个字符的IO流
输入
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
#上面三个函数的返回值为int,因为EOF常实现为-1,返回int就能与之比较

判断出错或者结束
int ferror(FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp);
#清除error或者eof标志

输出
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

每次一行的IO流
#include <stdio.h>
#输入
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
#gets由于没有指定缓冲区,所以有可能造成缓冲区溢出,要小心

#输出
int fputs(char *restrict buf, FILE *restrict fp);
int puts(const char *buf);

#格式化输出IO流
printf 输出到标准输出
fprintf 输出到指定流
sprintf 输出到指定数组
snprintf 输出到指定数组并在数组的尾端自动添加一个null字节

#格式化输入IO流
scanf 从标准输入获取
fscanf 从指定流获取
sscanf 从指定数组获取

③高级网络IO,是在linux中,针对网络套接字使用的IO,具体函数在第三点中详述。

注意:文件IO和高级网络IO合称为UNIX IO,因为是UNIX系统独有的。

3、高级网络IO

①read和write

这是最底层的IO函数,基本其他linux或者C库的IO读写函数都是基于对这两个函数的封装。并且read和write函数无论是网络数据读取或者磁盘文件读取,都适用并且都是最底层的函数。所以这两个函数既属于文件IO,又属于高级网络IO。

②recv和send

int recv( SOCKET s, char FAR *buf, int len, int flags);

int send( SOCKET s, const char FAR *buf, int len, int flags );

带一个进程缓冲区的IO读写,只适用于网络读写,一般用于TCP读写

③readv和writev

带多个进程缓冲区的IO读写,既可以适用于网络套接字读写, 也可以使用于磁盘文件读写

ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};

④sendto和recvfrom

int sendto(int s, const void *buf, int len, unsigned int flags,const struct sockaddr *to, int tolen);
int recvfrom(int s, void *buf, int len, unsigned int flags,struct sockaddr *from, int *fromlen);

可以看到,这两个函数的入口参数中有struct sockaddr结构体,这个结构体中存储了对端的IP和端口信息,所以这两个读写函数不需要connect和accept,就可以直接实现网络通信,所以UDP通信一般用这个函数,只限于网络套接字读写。如图所示:

linux中IO函数(仅限于读写)的总结_c++


udp中,recvfrom相当于connect+recv

sendto相当于connect+send(如果前面执行过recvfrom或sendto,这里的connect就不用执行了吧)

tcp中,recvfrom相当于recv+getpeername()

sendto相当于send+getsockname().

⑤recvmsg和sendmsg

这两个函数是最全的IO读写函数,包括了read,readv,recv和recvfrom的功能,sendmsg也是如此。

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; /* protocol address */
socklen_t msg_namelen; /* size of protocol address */
struct iovec *msg_iov; /* scatter/gather array */
int msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data (cmsghdr struct) */
socklen_t msg_controllen; /* length of ancillary data */
int msg_flags; /* flags returned by recvmsg() */
};

总结:

①读写函数并不涉及到阻塞非阻塞,如果操作的文件描述符时阻塞的,那么就会阻塞,文件描述符不阻塞,那么就不阻塞。

②对以上五种IO做总结,如下图所示

linux中IO函数(仅限于读写)的总结_c_02


4、五种IO模型


参考博客:

​http://www.360doc.com/content/18/0512/20/36367108_753420333.shtml​

​https://www.jianshu.com/p/f86e3a326b02​