进程是满足用户需求的一系列正在执行的任务。进程这个概念不是一开始就存在的,是随着人们需要处理的业务逻辑越来越复杂,需要并发执行多种任务,分时分配CPU资源,才逐渐发展出来的一个概念。进程在Linux中被组织为父子关系,子进程退出时,父进程需要调用wait或waitpid函数等待回收子进程的资源,否则子进程就一直以“僵尸”状态存在。
1、ptrace系统调用
ptrace系统调用,可以强制让一个进程成为另外一个进程的父进程,可以深入的对子进程进行流程控制。在Linux下有几个常用工具是基于ptrace系统调用的,大多用于做普通的程序分析,尤其是内存数据的审查和修改。
ptrace是以线程为单位的,如果一个进程有多个线程,父进程就需要分别ptrace所有的线程。这的动作存在一定的风险性,如果一个多线程的程序在设计的时候没有考虑支持ptrace,那么需要谨慎使用,否则容易影响到进程整体的执行。
一些基于ptrace实现的常用工具:
- strace: 打印进程调用的所有系统调用;
- ltrace: 打印进程涉及的库函数调用的分析;
- ftrace: 是一个跟踪二进制内容的函数调用的工具;
- gdb attach: 可以直接使用gdb进行进程attach。
2、资源锁和资源限制
内核中的资源锁有:
- 自旋锁
- 信号量
- 互斥锁
- 读写锁
- 顺序锁
- RCU锁
- futex锁
每一种锁都是用于解决某一种类型的问题。
futex是唯一一种由用户空间进程使用的锁,其实用户态很多高级语言的锁就是封装的futex,因为其高效,行为又符合要求。futex的核心思想就是直接将内核态的这个锁变量mmap映射到用户空间中,这样各个用户进程就可以在自己的空间中直接查询这个值了,不用再进入到内核态。但是,如果要写入时,仍然是需要通过Linux提供的API陷入内核来加锁的。
最有效的资源访问方式是不加锁,大部分的问题都可以使用无锁设计解决。但这也会带来内存上更大的损耗和代码管理上的难题。
互斥概念与同步概念
- 互斥是指同一时间只有一个进程可以访问资源,没有时序概念;
- 同步则包含了多个访问该资源进程的访问的先后顺序。
- 信号量是属于同步概念的,因为未得到资源的进程会睡眠等待。其他的内核锁是互斥概念的,如自旋、顺序,得不到就阻塞。
资源限制常用cgroup,随着Docker的成熟,得到了广泛的使用。cgroup可以限制cpu、内存、文件、行为等,甚至是系统调用。
在这之前,也可以独立使用getrlimit和setrlimit这两个系统调用进行资源限制。
如果是在做一个可发布的应用程序,rlimit仍是一个不错的资源限制的选择。
3、进程对系统内存的使用
- 大部分进程通过glibc申请使用内存,但glibc也是一个应用程序库,它最终是调用操作系统的内存管理接口来使用内存。
- 少数情况下,glibc在处理大量并发小内存的时候会出现内存泄漏。
- 进程申请了1GB内存,但并不一定需要这么多的物理内存。内核虽然也批准了,但只有在进程需要实际使用内存而引发缺页异常时,才会真正给它安排实际的内存。当所有进程都要求兑现申请的全部内存时,内核不一定能做到全部兑现。因此会崩溃。这个情况下会根据当前每个进程的OOM分数选择当前分数最高的进程直接杀死。这些操作的记录可以在/var/log/kern.log中查看到。
进程的内存种类:
- 堆,用来存放数据;
- 栈,用于执行进程;
- VIRT,进程的虚拟地址空间大小;
- RSS,进程实际正在使用的物理地址的大小;
查看一个进程资源的最好方法是使用/proc/pid/stat*中的三个文件:
- statm: 是关于进程的内存使用信息的;
- status: 是一个可读性好的、用户通常会比较关心的内容。
- stat: 是非常全面的信息,如进程号、缺页次数、启动时间、信号、CPU、进程组等。
此外,smaps中有非常详细的进程内部内存的映射情况。maps提供了内存映射的一个概览。
操作系统层面与内存分配有关的两个系统调用:
- brk
- mmap
这两种方式都是分配虚拟内存,没有分配物理内存。
4、多进程与进程通信
进程是被制造出来的一个概念,只是一个存在于理论上的概念模型,它实际上是一种结构体task_struct,使用pid进行定位。是内核如何处理进程概念的引入带来的一系列代价。
目前用户进程通信,最好的选择仍旧是System v IPC(POSIX IPC)的三种方法:
- 信号量,一般用于资源限制
- 共享内存,因为需要手动地组织内存的安排,非常复杂;
- 消息队列,是比较常见的通信方案
有大量数据通信需求的很多服务也喜欢采用UNIX Domain Socket,一个原因是这个方式的使用与普通socker方式没什么区别。
内核与用户空间的进程通信:Netlink
Netlink是用户程序与内核通信的socket方法。通过Netlink可以获得修改内核的配置。
Netlink功能模块:
- NETLINK_ROUTE,用来与邻居表、路由表、数据包分类器、网卡信息等子系统通信,获取信息或者进行设置。
- NETLINK_W1
- NETLINK_USERSOCK
- NETLINK_FIREWALL
- NETLINK_INET_DIAG,用于同网络诊断模块通信
- NETLINK_SELINUX
- NETLINK_ISCSI
- NETLINK_AUDIT
- NETLINK_CONNECTOR
- NETLINK_GENERIC
- NETLINK_CRYPTO
inet_diag模块:
inet_diag和tcp_diag是两个模块,但都是使用inet_diag的接口。inet_diag又是使用Netlink的接口。
经常使用的ss命令,就是使用的inet_diag模块实现的。