进程基础
1.进程定义
进程(Process)是计算机中已运行程序的实体。用户下达运行程序的命令后,就会产生进程。进程需要一些资源才能完成工作,如CPU使用时间、存储器、文件以及I/O设备,且为依序逐一进行,也就是每个CPU核心任何时间内仅能运行一项进程。简单说,进程就是代码运行的实体。
2.PID与PPID
PID全称Process ID,是标识和区分进程的ID。Linux系统保证不会同时存在两个进程拥有相同的PID,但在一个进程结束之后,其PID可能会再次被分配给新进程。
每个进程除了一定有PID还会有PPID,也就是父进程ID,通过PPID可以找到父进程的信息。
为什么进程都会有父进程ID呢?因为进程都是由父进程衍生出来的,实际上有一个PID为1的进程是由内核创建的init进程,其他子进程都是由它衍生出来,所以前面的描述并不准确,进程号为1的进程并没有PPID。
3.进程参数
任何进程启动时都可以赋予一个字符串数组作为参数,一般名为ARGV或ARGS。
通过解析这些参数可以让你的程序更加通用,例如cp命令通过给定两个参数就可以复制任意的文件,当然如果需要的参数太多最好还是使用配置文件。
进程参数一般可分为两类:Argument及Flag
进程参数只有在启动进程时才能赋值,如果需要在程序运行时进行交互,就需要了解进程的输入与输出了。
4.进程输入与输出
每个进程操作系统都会分配三个文件资源,分别是标准输入(STDIN)、标准输出(STDOUT)和错误输出(STDERR)。通过这些输入流,我们能够轻易得从键盘获得数据,然后在显示器输出数据。
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}
5.进程越多越好?
多进程的并行可以提高并发度,那么进程是越多越好?一般遇到这种问题都回答不是,事实上,很多大型项目都不会同时开太多进程。
举个Nginx的例子:多进程有一个坏处就是带来了CPU上下文切换时间,所以一味提高进程个数反而使系统性能下降。当然如果当前进程小于CPU个数,就没有充分利用多核的资源,所以Nginx建议Worker数应该等于CPU个数。
6.进程状态
进程总共有以下7种状态:
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
使用ps aux可以查看进程状态:
O:进程正在处理器运行,这个状态从来没有见过.
S:休眠状态(sleeping)
R:等待运行(runable)R Running or runnable (on run queue) 进程处于运行或就绪状态
I:空闲状态(idle)
Z:僵尸状态(zombie)
T:跟踪状态(Traced)
B:进程正在等待更多的内存页
D: 不可中断的深度睡眠,一般由IO引起,同步IO在做读或写操作时,cpu不能做其它事情,只能等待,这时进程处于这种状态,如果程序采用异步IO,这种状态应该就很少见到了。
其中就绪状态表示进程已经分配到除CPU以外的资源,等CPU调度它时就可以马上执行了。运行状态就是正在运行了,获得包括CPU在内的所有资源。等待状态表示因等待某个事件而没有被执行,这时候不耗CPU时间,而这个时间有可能是等待IO、申请不到足够的缓冲区或者在等待信号。
7.退出码
任何进程退出时,都会留下退出码,操作系统根据退出码可以知道进程是否正常运行。
退出码是0到255的整数,通常0表示正常退出,其他数字表示不同的错误。
例如:exit status 2
退出码能做什么?
在写Bash脚本时,可以根据前一个命令的退出码选择是否执行下一个命令。
8.进程文件
在Linux中“一切皆文件”,进程的一切运行信息(占用CPU、内存等)都可以在文件系统找到,例如看一下PID为1的进程信息:
xiaoju@roadnet-mapdata-server00.py:~/mapdata/V2020042014/shmapFiles$ ls /proc/1
attr cgroup comm cwd fd io map_files mountinfo net oom_adj pagemap root sessionid stack status timers
autogroup clear_refs coredump_filter environ fdinfo limits maps mounts ns oom_score personality sched setgroups stat syscall uid_map
auxv cmdline cpuset exe gid_map loginuid mem mountstats numa_maps oom_score_adj projid_map schedstat smaps statm task wchan
9.死锁与活锁
死锁(Deadlock)就是一个进程拿着资源A请求资源B,另一个进程拿着资源B请求资源A,双方都不释放自己的资源,导致两个进程都进行不下去。
举个很简单的例子,两个人相向过独木桥,他们同时向一边谦让,这样两个人都过不去,然后二者同时又移到另一边,这样两个人又过不去了。如果不受其他因素干扰,两个人一直同步在移动,但外界看来两个人都没有前进,这就是活锁。
活锁会导致CPU耗尽的,解决办法是引入随机变量、增加重试次数等。
10.POSIX
POSIX(Portable Operation System Interface)是一种操作系统的接口标准,Unix和Linux以及Mac OS都遵循这套接口标准。
目前很多编程语言(Go、Java、Python、Ruby等)都是天生跨平台的,因此我们很少注意系统调用的兼容性。实际上POSIX提供了这些语言上跨平台的语义,而且这是源码级别的保证。
(1)POSIX进程
运行程序时,操作系统通过POSIX定义的fork和exec接口创建起一个POSIX进程,这个进程就可以使用通用的IPC、信号等机制。
(2)POSIX线程
POSIX也定义了线程的标准,包括创建和控制线程的API,在Pthreads库中实现。
11.创建进程
通过操作系统暴露的API接口,可以创建进程,这就是系统调用。Linux或者其他Unix-like系统都提供了fork()和exec()等接口,Bash或者我们写的程序都可以通过调用这些接口来操作进程。
go创建进程
Go已经封装了与进程相关的接口,主要在os/exec这个Package中。
网友评论