linux 内核一开始提供了 read 与 write 阻塞式操作。
如果出现了很多的客户端连接,比如1000个,那么应用程序就会启用1000个进程或线程阻塞等待。此时会出现性能问题:
此时,Linux 内核一开始提供了 read 与 write 非阻塞式操作,可以通过socket设置SOCK_NONBLOCK标记 。
Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior of
socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the open file description (see open(2)) referred to by the new file descriptor. Using this flag saves extra calls to fcntl(2) to achieve
the same result.
从这里可以看出来 socket Linux 2.6.27内核开始支持非阻塞模式。
同理,当出现了很多的客户端连接,比如1000个,那就会触发1000次系统调用。(1000次系统调用开销也很客观)
因此,有了 select。
此时,Linux 内核一开始提供了 select 操作,可以把1000次的系统调用,简化为一次系统调用,轮询发生在内核空间。
SYNOPSIS
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
DESCRIPTION
select() allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file
descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.
select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) and epoll(7) do not have this limitation. See BUGS.
可以看到支持传输多个文件描述符交由内核轮询。
虽然从1000次系统调用,降为一次系统调用的开销,但是系统调用开销中需要传参1000个文件描述符。这也会造成一定的内存开销。
因此,有了 epoll。
select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) and epoll(7) do not have this limitation. See BUGS.
man epoll
man 2 epoll_create
man 2 epoll_ctl
man 2 epoll_wait
SYNOPSIS
#include <sys/epoll.h>
DESCRIPTION
The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered or a
level-triggered interface and scales well to large numbers of watched file descriptors.
The central concept of the epoll API is the epoll instance, an in-kernel data structure which, from a user-space perspective, can be considered as a container for two lists:
• The interest list (sometimes also called the epoll set): the set of file descriptors that the process has registered an interest in monitoring.
• The ready list: the set of file descriptors that are "ready" for I/O. The ready list is a subset of (or, more precisely, a set of references to) the file descriptors in the interest list.
The ready list is dynamically populated by the kernel as a result of I/O activity on those file descriptors.
内核会产生一个epoll 实例数据结构并返回一个文件描述符epfd。
对文件描述符 fd 和 其监听事件 epoll_event 进行注册,删除,或者修改其监听事件 epoll_event 。
SYNOPSIS
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
DESCRIPTION
This system call is used to add, modify, or remove entries in the interest list of the epoll(7) instance referred to by the file descriptor epfd. It requests that the operation op be performed
for the target file descriptor, fd.
Valid values for the op argument are:
EPOLL_CTL_ADD
Add an entry to the interest list of the epoll file descriptor, epfd. The entry includes the file descriptor, fd, a reference to the corresponding open file description (see epoll(7)
and open(2)), and the settings specified in event.
EPOLL_CTL_MOD
Change the settings associated with fd in the interest list to the new settings specified in event.
EPOLL_CTL_DEL
Remove (deregister) the target file descriptor fd from the interest list. The event argument is ignored and can be NULL (but see BUGS below).
阻塞等待注册的事件发生,返回事件的数目,并将触发的可用事件写入epoll_events数组中。
man 2 mmap
man 2 sendfile
man 2 fork
就是在用户的虚拟地址空间中寻找空闲的一段地址进行对文件的操作,不必再调用read、write系统调用,它的最终目的是将磁盘中的文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,减少了文件复制的开销,提高了用户的访问效率。
以读为例:
kafka的数据文件就是用的mmap,写入文件,可以不经过用户空间到内核的拷贝,直接内核空间落盘。
再比如JAVA中的MAppedByteBuffer底层在Linux就是mmap。
sendfile系统调用在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了数据在内核缓冲区和用户缓冲区之间的拷贝,操作效率很高,被称之为零拷贝。
比如 kafka,消费者进行消费时,kafka直接调用 sendfile(Java中的FileChannel.transferTo),实现内核数据从内存或数据文件中读出,直接发送到网卡,而不需要经过用户空间的两次拷贝,实现了所谓"零拷贝"。
再比如Tomcat、Nginx、Apache等web服务器返回静态资源等,将数据用网络发送出去,都运用了sendfile。
man 2 fork
创建子进程有三种方式:
现在Linux中是采取了Copy-On-Write(COW,写时复制)技术,为了降低开销,fork最初并不会真的产生两个不同的拷贝,因为在那个时候,大量的数据其实完全是一样的。 写时复制是在推迟真正的数据拷贝。若后来确实发生了写入,那意味着父进程和子进程的数据不一致了,于是产生复制动作,每个进程拿到属于自己的那一份,这样就可以降低系统调用的开销。
NOTES
Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it incurs is the time and memory required to duplicate the parent's page tables, and to create a unique
task structure for the child.