对于 Linux 来说正规的文件系统为 EXT2,一个文件系统存储的数据通常包括文件权限和属性,以及文件数据,这两部分分别存储在不同的地方。
系统平均负载:是处于可运行或不可中断状态的平均进程数。可运行进程:使用 CPU 或等待使用 CPU 的进程,不可中断状态进程:正在等待某些 IO 访问,一般是和硬件交互,不可被打断(不可被打断的原因是为了保护系统数据一致,防止数据读取错误)
即便是在多核处理器的计算机上,进程的数量通常也是远远多于处理器数量的。因此,宏观上并行运行的多个进程在微观上往往属于分时复用。进程调度的本质是怎么样让进程更好地分时复用处理器资源。概括地说,进程调度包括调度策略和进程切换两个重要话题。针对一个处理器来说,分时复用无非就是A进程的时间配额用完以后换到B进程的事情。在这个场景下,“如何选择B进程”就是调度策略,“如何运行B进程”就是进程切换。
我打算让volumio运行在我2009年购买的老爷机笔记本上,也让它发挥一点余温热。正常操作是将volumio的系统镜像刷到U盘上,连接电脑后使用U盘启动系统即可。但是家里没有找到合适的U盘(穷~~),加上前段时间听了同事关于linux内核的分享,感慨自己对系统的理解不够。因此我决定使用无盘启动volumio顺便研究一下linux启动原理。
如今的网络应用早已从 CPU 密集型转向了 I/O 密集型,网络服务器大多是基于 C-S 模型,也即 客户端 - 服务端 模型,客户端需要和服务端进行大量的网络通信,这也决定了现代网络应用的性能瓶颈:I/O。
最后一章列举本文参考过的书籍和项目,欢迎大家补充和讨论更多有关进程的知识。
Cgroups全称Control Groups,是Linux内核用于资源隔离的技术。目前Cgroups可以控制CPU、内存、磁盘访问。使用Cgroups是在Linux 2.6.24合并到内核的,不过项目在不断完善,3.8内核加入了对内存的控制(kmemcg)。要使用Cgroups非常简单,阅读前建议看sysadmincasts的视频,https://sysadmincasts.com/episodes/14-introduction-to-linux-control-groups-cgroups。
Epoll是poll的改进版,更加高效,能同时处理大量文件描述符,跟高并发有关,Nginx就是充分利用了epoll的特性。讲这些没用,我们先了解poll是什么。PollPoll本质上是Linux系统调用,其接口为int poll(struct pollfd *fds,nfds_t nfds, int timeout),作用是监控资源是否可用。
我们要想启动一个进程,需要操作系统的调用(system call)。实际上操作系统和普通进程是运行在不同空间上的,操作系统进程运行在内核态(todo: kernel space),开发者运行的进程运行在用户态(todo: user space),这样有效规避了用户程序破坏系统的可能。如果用户态进程想执行内核态的操作,只能通过系统调用了。
我们知道信号是进程间通信的其中一种方法,当然也可以是内核给进程发送的消息,注意信息只是告诉进程发生了什么事件,而不会传递任何数据。这是进程这个概念设计时就考虑到的了,因为我们希望控制进程,就像一个小孩我们想他按我们的想法做,前提就是他能够接受信号并且理解信号的含义。
IPC全称Interprocess Communication,指进程间协作的各种方法,当然包括共享内存,信号量或Socket等。管道(Pipe)管道是进程间通信最简单的方式,任何进程的标准输出都可以作为其他进程的输入。信号(Signal)下面马上会介绍。消息队列(Message)和传统消息队列类似,但是在内核实现的。共享内存(Shared Memory)后面也会有更详细的介绍。
当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。理解了孤儿进程和僵尸进程,我们临时加了守护进程这一小节,守护进程就是后台进程吗?
本章开始时演示了Hello World程序,其实已经创建了新的进程,通过Bash或者zsh这些Shell很容易创建新的进程,但Shell本身是怎么实现的呢?我们又能不能用Go实现类似Shell的功能呢?系统调用原来这一切都是操作系统给我们做好的,然后暴露了使用的API接口,这就是系统调用。
在Linux中“一切皆文件”,进程的一切运行信息(占用CPU、内存等)都可以在文件系统找到,例如看一下PID为1的进程信息。
任何进程退出时,都会留下退出码,操作系统根据退出码可以知道进程是否正常运行。退出码是0到255的整数,通常0表示正常退出,其他数字表示不同的错误。示例程序package mainfunc main() { panic("Call panic()")}运行结果root@fa13d0439d7a:/go/src# go run exit_code.gopanic: Call panic()goroutine 16 [running]:runtime.
根据进程的定义,我们知道进程是代码运行的实体,而进程有可能是正在运行的,也可能是已经停止的,这就是进程的状态。网上有人总结进程一共5种状态,也有总结是8种,究竟应该怎么算呢,最好的方法还是看Linux源码。进程状态的定义在fs/proc/array.c文件中。/** The task state array is a strange "bitmap" of* reasons to sleep.
前面提到多进程的并行可以提高并发度,那么进程是越多越好?一般遇到这种问题都回答不是,事实上,很多大型项目都不会同时开太多进程。下面以支持100K并发量的Nginx服务器为例。举个例子: NginxNginx是一个高性能、高并发的Web服务器,也就是说它可以同时处理超过10万个HTTP请求,而它建议的启动的进程数不要超过CPU个数,为什么呢?
任何进程启动时都可以赋予一个字符串数组作为参数,一般名为ARGV或ARGS。通过解析这些参数可以让你的程序更加通用,例如cp命令通过给定两个参数就可以复制任意的文件,当然如果需要的参数太多最好还是使用配置文件。获得进程Argument进程参数一般可分为两类,一是Argument,也就是作为进程运行的实体参数。例如cp config.yml config.yml.bak的这两个参数。
每个进程都一定有进程名字,例如我们运行top,进程名就是“top”,如果是自定义的程序呢?其实进程名一般都是进程参数的第一个字符串,在Go中可以这样获得进程名。package mainimport ( "fmt" "os")func main() { processName := os.Args[0] fmt.Println(processName)}进程的输出结果如下。root@87096bf68cb2:/go/src# go run process_name.
每个进程除了一定有PID还会有PPID,也就是父进程ID,通过PPID可以找到父进程的信息。为什么进程都会有父进程ID呢?因为进程都是由父进程衍生出来的,后面会详细介绍几种衍生的方法。那么跟人类起源问题一样,父进程的父进程的父进程又是什么呢?
关注时代Java