常见但不感知的系统调用:Socket、文件等

常用但不感知的一些等层的系统调用过程

线程相关系统调用

创建线程

创建线程,并指定运行属性(运行的函数、参数等);

int pthread_create(pthread_t*thread,const
pthread_attr_t*attr,void*(*start_routine)(void*),void*arg)

socket相关系统调用

Socket的相关状态:

  1. 未初始化(Uninitialized):Socket对象已经被创建,但还未进行初始化或绑定。
  2. 已创建(Created):Socket对象已经被创建,并且已经初始化,但还未绑定到特定的IP地址和端口号。
  3. 已绑定(Bound):Socket已经绑定到了一个特定的本地IP地址和端口号,可以用于监听连接或进行通信。
  4. 监听(Listening):只适用于服务器端Socket。Socket正在监听传入的连接请求,处于等待连接状态。
  5. 连接已建立(Connected):只适用于客户端Socket。Socket已经成功与服务器端建立连接。
  6. 可读(Readable):Socket接收缓冲区中有数据可供读取。
    • 连接建立完成,服务端的Socket即可读;
    • 数据到达,服务端的Socket读缓冲区内有数据就可读;
  7. 可写(Writable):Socket发送缓冲区有足够的空间可以写入数据。
    • 发送缓冲区空间充足即可写;
    • 连接建立后,客户端的Socket即可写;
  8. 连接关闭(Closed):Socket已经关闭,不能再进行读写或通信。
  9. 连接中断(Disconnected):Socket的连接由于某种原因被中断或丢失。

1. 创建socket

返回一个仅指明协议,未绑定socket地址的socket文件描述符;

int socket(int domain,int type,int protocol)
  • domain:使用的底层协议族;
    • TCP/IP协议族中可选:PF_INET、PF_INET6
  • type:指定服务类型;SOCK_STREAM\SOCK_DGRAM和SOCK_NONBLOCK\SOCK_CLOEXEC之间相与
    • TCP/IP协议族中,SOCK_STREAM常用于TCP,SOCK_DGRAM常用与UDP;
    • SOCK_NONBLOCK:设置其非阻塞;
    • SOCK_CLOEXEC:fork子进程时是否关闭此socket
  • protocol:在上面两个参数下仍还有可选的协议,使用此参数,一般不用,直接设为0

2. 命名socket

指定socket文件描述符(sockfd)绑定一个socket地址;

针对同一个addr,可以同时绑定多个socket,比如同时绑定tcp、udp;

int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen)
  • my_addr:socket地址;
  • addrlen:地址长度;

3. 监听socket

socket绑定了地址后,仍不能接受连接;

需要监听此socket,本质是创建一个监听队列,来存放待处理处理的客户端连接;

int listen(int sockfd,int backlog)
  • sockfd:监听的文件描述符;
  • backlog:全连接队列长度上限(通常完整连接数比backlog大);(2.2版本后)
    • 半连接队列上限由/proc/sys/net/ipv4/cp_max_syn_backlog设置

4. 接受连接accept

本质是服务端从全连接队列中获取一个socket; socket已经完成3次握手,处于ESTABLISHED状态;

int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen)
  • sockfd:已经监听过的socket文件;
  • addr:存放远端socket地址;accept成功后通过与之通信;

5. 建立连接connect

客户端通过此函数与服务端主动建立连接

int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen)
  • sockfd:连接建立完成后,由此socket标识此连接,进行读写通信
  • serv_addr:服务端socket地址
  • addrlen:地址长度

6. 关闭连接close

close:并非关闭连接,而是fd引用计数-1,当引用计数为0则关闭连接; 多进程的程序中,每次fork都会将父进程的socket引用计数+1; shutdown:立即终止连接

int close(int fd)    // 引用计数-1
int shutdown(int sockfd,int howto)    // 立即终止连接
  • fd:要关闭的socket文件描述符
  • howto:关闭的操作
    • SHUT_RD:关闭读,此socket的缓冲区都会丢弃;不可读;
    • SHUT_RD:关闭写,不可再执行写操作;
    • SHUT_RDWR:关闭读写;

7. 配置socket

// 获取socket配置
int getsockopt(int sockfd,int level,int option_name,void*option_value,
socklen_t*restrict option_len)
// 设置socket配置
int setsockopt(int sockfd,int level,int option_name,
const void*option_value, socklen_t option_len)

可用选项:

  • SO_RCVBUF:接受缓冲区大小;最小256字节 SO_SNDBUF:发送缓冲区大小;最小2048字节,确保足够的空闲处理拥塞
  • SO_RCVLOWAT:接受缓冲区低水位标记;默认1 byte SO_SNDLOWAT:发送缓冲区低水位标记;默认1 byte 用于IO复用时判断socket是否可读可写,当缓冲区字节数大于低水位,通知应用程序,socket可读可写;
  • SO_REUSEADDR:强制使用处于TIME_WAIT的socket地址;

TCP数据读写

ssize_t recv(int sockfd,void*buf,size_t len,int flags)
ssize_t send(int sockfd,const void*buf,size_t len,int flags)

UDP数据读写

ssize_t recvfrom(int sockfd,void*buf,size_t len,int flags,
struct sockaddr*src_addr,socklen_t*addrlen)

ssize_t sendto(int sockfd,const void*buf,size_t len,int flags,
const struct sockaddr*dest_addr,socklen_t addrlen)

IO读写系统调用

传统io读写

一次完整的read/write将发生:

  • 4次切换:2次用户到内核的切换、2次内核到用户的切换;
  • 4次拷贝:2次CPU拷贝,2次DMA拷贝;

read

ssize_t read(int fd, void *buf, size_t count)
  • fd:要打开的文件描述符;
  • *buf:要读取到的内存缓冲区地址;
  • count:要读取的字节数

1、用户程序发起read(),CPU切换到内核态;首先检查数据是否在page cache中,如果可用,无需磁盘访问,直接从page cache拷贝数据到指定的用户缓冲区,read()返回; 2、如果page cache未命中,将执行磁盘读取;CPU请求DMA控制器,由DMA控制器去读取磁盘数据;将数据拷贝到内核缓冲区,不需要CPU参与;拷贝完成,通过中断通知CPU拷贝结果; 3、CPU响应中断后,将数据拷贝到用户缓冲区,read()函数返回;

write

ssize_t write(int fd, const void *buf, size_t count)
  • fd:要打开的文件描述符;
  • *buf:要写入的数据缓冲区地址
  • count:要读取的字节大小;

1、用户程序发起write(),CPU切换到内核态,根据数据缓冲区的地址,将数据拷贝到内核空间(因为用户空间不能直接访问内核空间数据) 2、CPU将数据写入page cache后,就立即返回,不需要等待写入磁盘; 3、写入page cache后,此页数据会被标记为脏页,由操作系统后台pdflush线程异步完成落盘操作; 4、如果数据的一致性要求比较高,可用使用:fsync()、fdatasync()函数,强制将page cache落盘;函数将阻塞,直到落盘成功或异常;

Zero Copy

IBM-Zero Copy Zero-Copy:是一种数据传输技术或优化策略;仅用于不对文件进行修改,直接发送数据到socket; 目标:是在数据传输过程中尽量减少数据的复制次数,从而降低CPU和内存的开销,提高数据传输的效率; 实现方式:

mmap
sendfile
splice
,可用实现零拷贝操作;

mmap

用于将文件映射到进程的地址空间,将文件数据映射为内存中的一段虚拟地址,从而允许应用程序通过内存访问文件数据; 使用mmap,可用做到不在用户空间和内核空间进行数据拷贝,可以方便地对文件进行随机访问,实现读写操作; 对于需要对文件进行读写修改的操作,mmap相比于read/write更高效;

sendfile

1、调用sendfile(),切换到内核态; 2、查询PageCache,有数据直接CPU Copy即可;无数据,通过DMA发起IO请求; 3、DMA复制数据到PageCache,并异步通知CPU完成; 4、CPU Copy数据到Socket缓冲区,再通过DMA拷贝到网卡,完成发送; 5、sendfile()返回,切换回用户态;

与read/write相比:

  • 少了两次上下文切换;和一次CPU Copy;但是sendfile无法修改数据,用户线程完全不参与数据操作;
  • CPU直接将内核PageCache数据,拷贝至Socket缓冲区;减少拷贝到用户态的次数;
  • 只能用于文件到Socket的拷贝;