Linux select/poll/epoll 原理(三)poll 实现

针对 select 系统调用的三个不足,poll 解决的是第一个、最多 1024 个 FD 限制的问题。

其实现思路是:
1. 不再使用位图来传递事件和结果,而是使用 pollfd 。 结构体数组来传递。
2. 在内部实现时,以 poll_list 链表的形式来分批次保存 pollfd 。不像 select 那样一次申请完整的一大块内存。
3. 通过从进程的信号量里获取能打开的最大文件数量,解决 1024 个限制的问题。

0. 基本数据结构

// 源码位置:include/uapi/asm-generic/poll.h
struct pollfd {
    int fd;         // FD
    short events;   // 输入的敢兴趣事件
    short revents;  // 输出的结果
};

// 源码位置:fs/select.c
struct poll_list {
    struct poll_list *next;

    // entries 指向的数组里 pollfd 的数量
    int len;

    // 指向 pollfd 数组的指针
    struct pollfd entries[0];
};

pollfd 结构体用来传递单个FD的输入事件、输出结果。

poll_list 是一个链表,其节点指向 pollfd 结构体的数组,这个数组要么是在栈上预分配、要么是按内存页分配(保持页对齐)。

继续阅读

Linux select/poll/epoll 原理(二)select 实现

阅读源码时看到一个结构体的定义不知道在哪时,可以通过这个网站来查找,非常方便。

__user 是一个宏定义,表示用户空间的,内核不能直接使用,需要使用函数 copy_from_user/copy_to_user 进行处理。

0. 进程打开的文件

进程的表示:

// 源码位置:include/linux/sched.h
struct task_struct {
    // ....省略其他属性

    /* 文件系统信息: */
    struct fs_struct        *fs;

    /* 打开的文件信息: */
    struct files_struct     *files;

    // ....省略其他属性
}

进程维护打开的文件的数据结构:

// 源码位置:include/linux/fdtable.h
struct files_struct {
  /*
   * read mostly part
   */
    atomic_t count;
    bool resize_in_progress;
    wait_queue_head_t resize_wait;

    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    unsigned int next_fd;
    unsigned long close_on_exec_init[1];
    unsigned long open_fds_init[1];
    unsigned long full_fds_bits_init[1];
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

struct fdtable {
    // 进程能打开的最大文件数
    unsigned int max_fds;
    struct file __rcu **fd;         /* current fd array */
    unsigned long *close_on_exec;

    // 当前打开的一组文件
    unsigned long *open_fds; 
    unsigned long *full_fds_bits;
    struct rcu_head rcu;
};

static inline bool fd_is_open(unsigned int fd, const struct fdtable *fdt)
{
    return test_bit(fd, fdt->open_fds);
}

小结:进程打开的文件维护在位图 fdtable.open_fds 里,对应的比特位为 1 表示文件打开,为 0 是关闭。

select 里传递事件也借鉴了这种思想,通过位图来传递,FD 对应的比特位为 1 表示对事件感兴趣或有事件发生。

继续阅读

Linux select/poll/epoll 原理(一)实现基础

本序列涉及的 Linux 源码都是基于 linux-4.14.143 。

1. 文件抽象 与 poll 操作

1.1 文件抽象

在 Linux 内核里,文件是一个抽象,设备是个文件,网络套接字也是个文件。

文件抽象必须支持的能力定义在 file_operations 结构体里。

在 Linux 里,一个打开的文件对应一个文件描述符 file descriptor/FD,FD 其实是一个整数,内核把进程打开的文件维护在一个数组里,FD 对应的是数组的下标。

文件抽象的能力定义:

// 源码位置:include/linux/fs.h
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);

    // 对于 select/poll/epoll 最重要的实现基础
    // 非阻塞的轮询文件状态的函数
    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    // 省略其他函数指针
} __randomize_layout;


// 源码位置:include/linux/poll.h
typedef struct poll_table_struct {
    // 文件的 file_operations.poll 实现一定会调用的队列处理函数
    poll_queue_proc _qproc;

    // poll 操作敢兴趣的事件
    unsigned long _key;
} poll_table;

// poll 队列处理函数
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

继续阅读

关于 Linux swap 的一切

一个问题

51放假前的时候,一个以前的同事说他们的系统出现 物理空闲内存为 0, swap 分区使用增长,增长到 3G 左右就会引起应用异常退出。但是 JVM 监控看到堆还是有大量空闲空间的。昨天又看到他在朋友圈求助这个问题,决定研究下。

按以前的理解,物理内存不够时,就会把一些不常用的内存页交换到 swap 空间,以释放内存。他碰到物理内存耗尽、swap 空间增长,这是很正常的。只是这些增长的内存干嘛用了。

free -m 命令能只看到物理内存、交换空间各使用了多少,但看不到是每个进程具体用了多少。

还是得靠 top 命令,进入 top 命令的显示界面后,按下 Shift+m 就可以按内存使用排序,如下图:
top demo

假如排在第一的是个 Java 进程,占用了 6G 内存,而JVM配置的最大堆内存是 4G,说明非堆内存(本地内存、线程栈等)占用了 2G。

后面跟那个同事求证,是他们最近上线的一个组件用了 Netty,使用不当导致本地内存泄漏。

关于 swap 的一切

原文: https://www.linux.com/news/all-about-linux-swap-space

Linux 把物理内存(RAM)分为一块一块,称为 页(page)。
交换是把内存页拷贝到提前配置在硬盘上的空间(称为交换空间)的过程,以释放页的内存。
物理内存和交互空间的组合就是可用的虚拟内存。

继续阅读

路由跟踪

traceroute

打印用于跟踪到网络主机的路由包。利用了IP协议的存活时间(time to live, TTL)字段和尝试引起到目的主机路径的每个网关的 ICMP TIME_EXCEEDED 响应。

Traceroute的工作原理(来自:http://www.cnblogs.com/peida/archive/2013/03/07/2947326.html

Traceroute最简单的基本用法是:traceroute hostname
Traceroute 程序的设计是利用ICMP及IP header的TTL(Time To Live)栏位(field)。首先,traceroute送出一个TTL是1 的IP datagram(其实,每次送出的为3个40字节的包,包括源地址,目的地址和包发出的时间标签)到目的地,当路径上的第一个路由器 (router)收到这个datagram时,它将TTL减1。此时,TTL变为0了,所以该路由器会将此datagram丢掉,并送回一个 「ICMP time exceeded」消息(包括发IP包的源地址,IP包的所有内容及路由器的IP地址),traceroute 收到这个消息后, 便知道这个路由器存在于这个路径上,接着traceroute 再送出另一个TTL是2 的datagram,发现第2 个路由 器...... traceroute 每次将送出的datagram的TTL 加1来发现另一个路由器,这个重复的动作一直持续到某个 datagram 抵达目的地。当datagram到达目的地后,该主机并不会送回ICMP time exceeded消息,因为它已是目的地了,那么 traceroute如何得知目的地到达了呢?
Traceroute 在送出UDP datagrams到目的地时,它所选择送达的port number 是一个一般应用程序都不会用的号码(30000 以上),所以当此 UDP datagram 到达目的地后该主机会送回一个「ICMP port unreachable」的消息,而当traceroute 收到这个消 息时,便知道目的地已经到达了。所以traceroute 在Server端也是没有所谓的Daemon 程式。
Traceroute提取发 ICMP TTL到期消息设备的IP地址并作域名解析。每次 ,Traceroute都打印出一系列数据,包括所经过的路由设备的域名及 IP地址,三个包每次来回所花时间。
     

继续阅读

tcpdump

tcpdump是Linux内置的一个抓包工具,用于对网络上的数据包进行截获、分析的工具。

完整命令格式

       tcpdump [ -AbdDefhHIJKlLnNOpqRStuUvxX ] [ -B buffer_size ] [ -c count ]
               [ -C file_size ] [ -G rotate_seconds ] [ -F file ]
               [ -i interface ] [ -j tstamp_type ] [ -m module ] [ -M secret ]
               [ -r file ] [ -s snaplen ] [ -T type ] [ -w file ]
               [ -W filecount ]
               [ -E spi@ipaddr algo:secret,...  ]
               [ -y datalinktype ] [ -z postrotate-command ] [ -Z user ]
               [ expression ]

继续阅读

命令的执行方式

这个东西很基础,也没想过专门写篇博客的,只是有人因为错误的命令的执行方式导致 “Command not found”,竟然连发两封邮件要求协助解决,我还是把自己知道的写出来。

执行可执行文件

执行文件就是具有可执行权限的文件,如果在文件所在目录上执行 llls -l命令时,可能看到如下结果:
-rwxr-xr-- 1 usr users 289 Jul 29 09:15 cronmonth
其中的x就表示文件的属主对文件具有可执行权限。

假设nginx的安装目录在 /usr/share/nginx/ ,它的可执行文件就是 /usr/share/nginx/sbin/nginx,有两种简单的方式可以启动nginx。

绝对路径方式

也就是从根目录 / 开始一直到可执行文件的完整路径: /usr/share/nginx/sbin/nginx

相对路径方式

先用 cd 跳转到可执行文件所在的目录,也就是先执行 cd /usr/share/nginx/sbin,再以相对当前位置的相对路径执行: ./nginx
同理,如果当前工作路径是在 /usr/share/nginx/conf,那么仍然可以用相对路径:../sbin/nginx

用相对路径而不是绝对路径的好处就是不用敲那么多字,但有些场合下还是用绝对路径好,比如cron脚本里调用其他脚本时,最好用绝对路径指定被调用脚本。
继续阅读

wget 备忘

今天要从github下个库很慢,突然来了这个命令:ssh coderbee@coderbee.net 'wget https://github.com/sbt/sbt-assembly/archive/master.zip && cat master.zip' > master.zip,把vps当作下载中转站了,速度还行。

下完后觉得wget还是很nice的,以前也用来下载过一些api文档,多学习点。

wget介绍

wget 是非交互式(可以在后台运行)的网络下载工具,支持的协议有HTTP、HTTPS、FTP。

wget 可以解析并获取HTML、XHTML和CSS页面里的链接,创建远程web页面的本地版本,完全重建原始页面的目录结构。可以命令wget转换已下载文件里的链接指向本地文件,用于离线浏览。

wget的健壮性可用于慢速或不稳定的网络连接;如果下载由于网络问题失败,wget会重试直到文件下载成功。还支持断点下载,如果服务器支持的话。

使用方式: wget [option]... [URL]...
继续阅读

ssh 命令说明与使用

ssh

简介

SSH(secure shell)是一种网络协议,用于计算机之间的加密登录。SSH有多种实现,Linux系统下使用的一般是OpenSSH。

来自维基百科:


传统的网络服务程序,如rsh、FTP、POP和Telnet其本质上都是不安全的;因为它们在网络上用明文传送数据、用户帐号和用户口令,很容易受到中间人(man-in-the-middle)攻击方式的攻击。就是存在另一个人或者一台机器冒充真正的服务器接收用户传给服务器的数据,然后再冒充用户把数据传给真正的服务器。

而SSH是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。通过SSH可以对所有传输的数据进行加密,也能够防止DNS欺骗和IP欺骗。

SSH之另一项优点为其传输的数据可以是经过压缩的,所以可以加快传输的速度。SSH有很多功能,它既可以代替Telnet,又可以为FTP、POP、甚至为PPP提供一个安全的“通道”。

使用形式


     ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]
         [-D [bind_address:]port] [-e escape_char] [-F configfile] [-I pkcs11]
         [-i identity_file] [-L [bind_address:]port:host:hostport]
         [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
         [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port]
         [-w local_tun[:remote_tun]] [user@]hostname [command]

继续阅读

ssh 自动登录

自动登录

SSH提供了公钥登录,可以省去每次登录都要输入的秘密的步骤。

公钥登录原理:

  1. 用户将自己的公钥存储在远程主机上。
  2. 登录的时候,远程主机向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。
  3. 远程主机利用事先存储的公钥进行解密,如果成功,就证明用户是可行的,直接允许登录shell,不再要求输入密码。

继续阅读