1-1关于Linux的进程详述
之所以希望对Linux有一个尽可能全面地了解,是因为我认为进程是Linux中非常重要的一环。
本章将了解以下内容:
1.什么是进程?
2.进程和程序有何区别?
3.进程知识的一些拓展
4.关于线程控制
1.什么是进程
进程是进程是程序在一个数据集合上的一次执行过程。
广义的进程其实有许多概念:
进程是一个独立的可调度的活动;
进程是一个抽象实体,当它执行某个任务时,要分配和释放各种资源;
进程是可以并行执行的计算单位;
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动;
2. 进程和程序有何区别?
2.1 概念
程序(procedure):程序就是执行一系列有逻辑、有顺序结构的指令,帮我们达成某个结果。我(或者说系统)对于它的期望就是:这个操作有什么效果。比如考试,老师给你一张试卷->你开始做->老师看到你的成绩,考试就是程序,你需要执行,写了就有分数,哪怕零分。
进程(process):进程是程序在一个数据集合上的一次执行过程,在早期的UNIX、Linux 2.4及更早的版本中,它是系统进行资源分配和调度的独立基本单位。就如上面的考试(程序),老师让你考试你执行了一个考试的程序,你做了“听力”这个进程,又做了“单项选择题”这个进程,还做了“作文“这个进程。你需要一步一步来,一题一题做。
为了方便理解,可以简而言之:程序是为了实现某种任务而设计的软件。进程?进行中的程序。
2.2 进程的特性
程序只是一些列指令的集合,是一个静止的实体,而进程不同,进程有以下的特性:
(1)动态性:进程的实质是一次程序执行的过程,有创建、撤销等状态的变化。而程序是一个静态的实体。
(2)并发性:进程可以做到在一个时间段内,有多个程序在运行中。程序只是静态的实体,所以不存在并发性。
(3)独立性:进程可以独立分配资源,独立接受调度,独立地运行。
(4)异步性:进程以不可预知的速度向前推进。
(5)结构性:进程拥有代码段、数据段、PCB(进程控制块,进程存在的唯一标志)。也正是因为有结构性,进程才可以做到独立地运行。
2.3 进程的分类
在Linux系统中,根据进程的特点,把进程可以分为三类:交互进程、批处理进程和守护进程。
交互进程:是由shell启动的进程,它既可以在前台运行,也可以在后台运行。交互进程在执行过程中,要求与用户进行交互操作。简单来说就是用户需要给出某些参数或者信息,进程才能继续执行
批处理进程:与windows原来的批处理很类似,是一个进程序列。该进程负责按照顺序启动其它进程。
守护进程:是是执行特定功能或者执行系统相关任务的后台进程。守护进程只是一个特殊的进程,不是内核的组成部分。许多守护进程在系统启动时启动,直到系统关闭时才停止运行。而某些守护进程只是在需要时才会启动,比如FTP或者Apache服务等,可以在需要的时候才启动该服务。
根据进程的状态又可以将其分为:守护进程、孤儿进程和僵尸进程(《在UNIX高级环境编程》书中也称为”僵死进程“)。
守护进程:所有守护进程都可以超级用户(用户ID为0)的优先权运行;守护进程没有控制终端;守护进程的父进程都是init进程(即1号进程)。
孤儿进程:一个父进程退出后,它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程(没有爸爸,要找个后爸ヽミ ´∀`ミノ<管着它)。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个子进程结束但是没有完全释放内存(在内核中的 task_struct没有释放),该进程就成为僵尸进程。僵尸进程会导致资源的浪费,而孤儿进程不会。关于孤儿进程和僵死进程会开一版贴来进一步认识。
3.进程拓展
进程有父子之分,那父子进程如何实现关系?
进程产生子进程由fork(vfork)创建。fork是一个系统调用,其效果是为当前进程创建一个新进程,此子进程(新进程)除了父进程的返回值和PID外都是一样的,如进程的文件描述、寄存器等等。vfork可以使的效率得到大幅度提升。
另一个系统调用exec() ,作用是切换子进程中的执行程序也就是替换其从父进程复制过来的代码段与数据段。
以下为简短的代码演示使用fork创建子进程:
int glob = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int main(int argc, char const *argv[])
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) /*(1)*/
{
err_sys("fork error");
}
else if (pid == 0) /* child */ /*(2)*/
{
glob++; /* modify variables */
var++;
}
else /*(3)*/
{
sleep(2); /* parent */
}
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
} //摘自《UNIX高级环境编程第二版》
毋须多关心代码除了(1)、(2)、(3)的其他内容。在(1)处创建子进程。在fork函数调用之后,新的进程将启动并和本进程一起从fork函数返回。但不同的是本进程的fork将返回新任务的pid,而新进程的fork将返回0。(注意就算创建成功,也还没有走相关的程序,需要exec())。
关于运行中的进程,我们可以通过进程树来方便的查看其父子关系。系统提供了进程树查看工具“tree”。去下查看树指令“pstree”:
systemd─┬─ModemManager─┬─{gdbus}
│ └─{gmain}
├─NetworkManager─┬─dhclient
│ ├─dnsmasq
│ ├─{gdbus}
│ └─{gmain}
├─VGAuthService
├─accounts-daemon─┬─{gdbus}
│ └─{gmain}
├─acpid
├─agetty
├─apt.systemd.dai───apt.systemd.dai───unattended-upgr
├─aptd───{gmain}
├─avahi-daemon───avahi-daemon
├─colord─┬─{gdbus}
│ └─{gmain}
├─cron
├─cups-browsed─┬─{gdbus}
│ └─{gmain}
├─cupsd
├─dbus-daemon
├─fwupd─┬─3*[{GUsbEventThread}]
│ ├─{fwupd}
│ ├─{gdbus}
│ └─{gmain}
├─gnome-keyring-d─┬─{gdbus}
│ ├─{gmain}
│ └─{timer}
├─lightdm─┬─Xorg───{InputThread}
│ ├─lightdm─┬─upstart─┬─at-spi-bus-laun─┬─dbus-daemon
│ │ │ │ ├─{dconf worke+
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─at-spi2-registr─┬─{gdbus}
│ │ │ │ └─{gmain}
可以直观地看到所有进程起始于“systemd”(另外一种是init),其即是所有进程的父进程或者祖父进程(简单记忆就是爸爸的爸爸)。
我们也可以使用<ps -fxo user,ppid,pid,pgid,command>来进一步确认进程的父子关系。pid即该进程的唯一标号,ppid即其父进程编号,command 表示的是该进程通过执行什么样的命令或者脚本而产生的。通过查看此表就可以知道ps是由bash 创建的。
USER PPID PID PGID COMMAND
apical-+ 21070 21071 21071 bash
apical-+ 21071 23394 23394 \_ ps -fxo user,ppid,pid,pgid,command
apical-+ 16212 16213 16213 bash
apical-+ 15848 15849 15849 bash
apical-+ 15766 15767 15767 bash
apical-+ 14318 14319 14319 bash
apical-+ 13738 13739 13739 bash
apical-+ 13333 13334 13334 bash
apical-+ 1708 2646 2646 /sbin/upstart --user
apical-+ 2646 2711 2709 \_ upstart-udev-bridge --daemon --user
apical-+ 2646 2724 2724 \_ dbus-daemon --fork --session --addres
apical-+ 2646 2736 2736 \_ /usr/lib/x86_64-linux-gnu/hud/window-
apical-+ 2646 2767 2765 \_ upstart-dbus-bridge --daemon --sessio
apical-+ 2646 2768 2766 \_ upstart-dbus-bridge --daemon --system
apical-+ 2646 2770 2769 \_ upstart-file-bridge --daemon --user
apical-+ 2646 2774 2773 \_ /usr/bin/fcitx
apical-+ 2646 2788 2788 \_ /usr/bin/dbus-daemon --fork --print-p
apical-+ 2646 2794 2793 \_ /usr/bin/fcitx-dbus-watcher unix:abst
apical-+ 2646 2801 2801 \_ /usr/lib/x86_64-linux-gnu/bamf/bamfda
完整地了解UNIX的进程控制是非常重要的。其中必须熟练掌握的只有几个函数–fork 、exec_族、_exit、wait和waitpid 。很多应用程序都使用这些原语。fork原语也给了我们一个了解竞争条件的机会。往后的文章逐步对这些函数进行学习。
参考:https://www.jianshu.com/p/b96f0c3d2d36
https://blog.youkuaiyun.com/qq_36812792/article/details/80118923