这里的进程锁与线程锁、互斥量、读写锁和自旋锁不同,它是通过记录一个PID文件,避免两个进程同时运行的文件锁。进程锁的作用之一就是可以协调进程的运行,例如crontab使用进程锁解决冲突提到,使用crontab限定每一分钟执行一个任务,但这个进程运行时间可能超过一分钟,如果不用进程锁解决冲突的话两个进程一起执行就会有问题。
关于这本书本书受理解Unix进程启发而作,用极简的篇幅深入学习进程知识。理解Linux进程用Go重写了所有示例程序,通过循序渐进的方法介绍Linux进程的工作原理和一切你所需要知道的概念。本书适合所有Linux程序员阅读。在线阅读,PDF下载。三位好朋友阅读前介绍三位即将与大家打交道的小伙伴:Linux、Go和Docker。
极简的篇幅深入学习进程知识
通常,现在的操作系统都支持多任务,意味着操作系统(给用户)造成了一种假象,(让用户觉得)它同时能够做多件事情,事实上,它是快速地轮换执行这些任务的。Linux 内核通过使用进程,来管理多任务。通过进程,Linux 安排不同的程序等待使用 CPU。有时候,计算机变得呆滞,运行缓慢,或者一个应用程序停止响应。
即便是在多核处理器的计算机上,进程的数量通常也是远远多于处理器数量的。因此,宏观上并行运行的多个进程在微观上往往属于分时复用。进程调度的本质是怎么样让进程更好地分时复用处理器资源。概括地说,进程调度包括调度策略和进程切换两个重要话题。针对一个处理器来说,分时复用无非就是A进程的时间配额用完以后换到B进程的事情。在这个场景下,“如何选择B进程”就是调度策略,“如何运行B进程”就是进程切换。
最后一章列举本文参考过的书籍和项目,欢迎大家补充和讨论更多有关进程的知识。
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