上一节重点是讲创建进程的部分,这一篇看看进程相关命令和pid等。
9.1 进程相关命令
在讲状态之前,先来看看进程相关命令,怎么查看一个进程的基本信息等。
9.1.1 ps
英文全拼:process status。命令用于显示当前进程的状态,类似于 windows 的任务管理器。
root@ubuntu:~/c_test/09# ps --help all
Usage:
ps [options]
基本选项:
-A, -e 全部进程
-a 显示同一个终端的所有进程
a 所有终端的进程,包含其他用户
-d all except session leaders
-N, --deselect 反向选择
r 运行的程序
T 该终端的所有进程
x 没有控制终端的进程
Selection by list:
-C <command> command name
-G, --Group <GID> 真实的组id或名称
-g, --group <group> 会话或有效的组名
-p, p, --pid <PID> 进程pid
--ppid <PID> 父进程ID
-q, q, --quick-pid <PID>
process id (quick mode)
-s, --sid <session> session id
-t, t, --tty <tty> terminal
-u, U, --user <UID> 有效的用户id或名称
-U, --User <UID> 真实的用户id或名称
The selection options take as their argument either:
a comma-separated list e.g. '-u root,nobody' or
a blank-separated list e.g. '-p 123 4567'
Output formats:
-F extra full
-f full-format, including command lines
f, --forest ascii art process tree
-H 展示进程的层次结构
-j jobs format
j BSD job control format
-l long format
l BSD long format
-M, Z add security data (for SELinux)
-O <format> preloaded with default columns
O <format> as -O, with BSD personality
-o, o, --format <format>
user-defined format
s signal format
u user-oriented format
v virtual memory format
X register format
-y do not show flags, show rss vs. addr (used with -l)
--context display security context (for SELinux)
--headers repeat header lines, one per page
--no-headers do not print header at all
--cols, --columns, --width <num>
set screen width
--rows, --lines <num>
set screen height
Show threads:
H as if they were processes
-L possibly with LWP and NLWP columns
-m, m after processes
-T possibly with SPID column
其他项:
-c show scheduling class with -l option
c show true command name
e 在命令后显示环境
k, --sort specify sort order as: [+|-]key[,[+|-]key[,...]]
L show format specifiers
n display numeric uid and wchan
S, --cumulative include some dead child process data
-y do not show flags, show rss (only with -l)
-V, V, --version display version information and exit
-w, w unlimited output width
--help <simple|list|output|threads|misc|all>
display help and exit
For more details see ps(1).
上面的参数比较多,我们列一下比较常用的命令:
1. ps x // 没有控制终端的进程(啥是没有控制终端,目前我也不清楚)
2. ps ax // 显示所有没有控制终端的进程
3. ps aux // 显示进程的全面信息
4. ps aux --sort -pcpu // 根据CPU使用来升序排序
5. ps aux --sort -pmem // 根据内存使用来升序排序
6. ps -f -C test_file // 通过进程名来查看进程信息 -f显示更多细节
7. ps -ef // 显示所有进程的更多细节
ps的参数确实很多,分为几大类,而且参数带-和不带-的可能意义也不同,比如:ps -ef就相当于ps -e -f。
以后不知道的可以查一下上面的help信息,并且也列了一些平时经常用的命令,其实这几个命令也够够的了。
下面还有一个链接,讲的挺不错的。
ps aux 和ps -ef都是显示进程的信息,那这两个有什么区别么:
ps -ef是标准格式显示进程的:
ps aux 是用BSD的格式来显示的:
其实感觉也差不多的。
9.1.2 top
先看帮助文档
交互式命令帮助 - procps-ng version 3.3.10
Window 1:Def: Cumulative mode Off. System: Delay 3.0 secs; Secure mode Off.
Z,B,E,e Global: 'Z' colors; 'B' bold; 'E'/'e' 修改内存的单位
l,t,m Toggle Summary: 'l' 平均值; 't' task/cpu stats; 'm' memory info
0,1,2,3,I Toggle: '0' 把0隐藏; '1/2/3' cpus or numa node views; 'I' Irix mode
f,F,X Fields: 'f'/'F' add/remove/order/sort; 'X' increase fixed-width
L,&,<,> . Locate: 'L'/'&' find/again; Move sort column: '<'/'>' left/right
R,H,V,J . Toggle: 'R' 按pid排序; 'H' 查看线程; 'V' 进程树查看方式; 'J' 对齐的
c,i,S,j . Toggle: 'c' Cmd name/line; 'i' 感觉是不显示空闲; 'S' Time; 'j' Str justify
x,y . Toggle highlights: 'x' sort field; 'y' 当前运行的任务
z,b . Toggle: 'z' color/mono; 'b' bold/reverse (only if 'x' or 'y')
u,U,o,O . Filter by: 'u'/'U' effective/any user; 'o'/'O' other criteria
n,#,^O . Set: 'n'/'#' max tasks displayed; Show: Ctrl+'O' other filter(s)
C,... . Toggle scroll coordinates msg for: up,down,left,right,home,end
k,r Manipulate tasks: 'k' kill; 'r' renice
d or s Set update interval
W,Y Write configuration file 'W'; Inspect other output 'Y'
q Quit
( commands shown with '.' require a visible task display window )
Press 'h' or '?' for help with Windows,
Type 'q' or <Esc> to continue
比较重要的命令:
1. t m // 比较友好的显示进程CPU的占用率,还有内存的占用率
2. 1 // 显示多个cpu
3. M // 根据内存使用量排序
4. P // 根据CPU占用率来排序
5. T // 根据进程运行时间的长短来排序
6. U // 可以根据后面输入的用户名来筛选进程
7. H // 查看线程
8. '<'/'>' 进行翻页
9. K // 可以根据后面输入的PID来杀死进程
9.1.3 kill
kill命令就比较简单了。
使用格式:
kill [-signal]
信号值以后再讲,还挺多信号,这个kill就是相等给pid的进程发送一个signal信息。
例:kill :这样就是优雅的杀死这个进程,进程中申请的变量啥的,都会释放完了才会被杀死。
kill -9 : 这个就是强制杀死,不过进程的资源释放不释放就直接杀死。
kill -9 -1 : 如果pid=-1的话,就相等于杀死所有进程,这个要慎用。
9.2 进程号
9.2.1 pid
通过上面的命令分析,有一个很重要的点就是pid。其实linux下每个进程都有一个非负整形表示的唯一进程ID,这就是pid。
pid其实就代表了这个进程,内核中的进程控制块,也是通过这个pid去查找的。
9.2.2 进程号相关函数
linux系统有提供专门获取进程号的函数,下面我们来看看。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); // 返回调用进程的进程ID
pid_t getppid(void); // 返回调用进程的父进程ID
uid_t getuid(void); // 调用进程的实际用户ID
uid_t geteuid(void); // 调用进程的有效用户ID
gid_t getgid(void); // 调用进程的实际组ID
gid_t getegid(void); // 调用进程的有效组ID
我们通过写一个例子来看看这几个函数:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
pid_t pid = -1;
pid = getpid();
printf("pid = %d\n", pid);
pid = getppid();
printf("ppid = %d\n", pid);
uid_t uid = -1;
uid = getuid();
printf("uid = %d\n", uid); // 哪个用户执行的进程就是那个用户id
uid = geteuid(); // 在没有修改前跟uid是一样的
printf("euid = %d\n", uid);
gid_t gid = -1;
gid = getgid();
printf("gid = %d\n", gid);
git = getegid();
printf("egid = %d\n", gid);
return 0;
}
执行的结果:
chen@ubuntu:~$ ./test_pid
pid = 1608
ppid = 1563
uid = 1000
euid = 1000
gid = 1000
egid = 1000
chen@ubuntu:~$ getent passwd 1000 # 通过uid获取用户名
chen:x:1000:1000:Ubuntu16_64,,,:/home/chen:/bin/bash
既然有获取就有修改:
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
这个设置是有权限的:
还有设置有效用户ID的:
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置用户ID。
特权用户可将有效用户ID设置为uid。
权限问题总是让人头大,不过也是因为有权限,才能保证数据不被其他用户读取。
9.2.3 怎么分配pid
既然一个pid对应者一个进程,大家想不想知道pid又是怎么分配的?
linux分配pid的算法,是采用了延迟重用的算法,即分配给新创建进程ID尽量不与最近终止的进程ID重复,因为好多程序会根据pid来做判断的,防止将新创建的进程误判为相同进程ID的已经退出的进程。
内核采用了什么方法:
- 也是使用位图记录进程ID的分配情况,(0为可用,1为在使用的进程)
- 将上次分配的进程ID记录到last_pid中,分配进程ID时,从last_pid+1开始找起,从位图中寻找可用的ID。
- 如果找到位图集合的最后一位仍不可用,则回滚到位图集合的起始位置,从头开始找。
回绕一般是从300开始,因为300以下的pid会被系统占用,而不能分配给用户进程。(就像网络端口一样)
我们可以查看这个进程pid的最大值:
chen@ubuntu:~$ cat /proc/sys/kernel/pid_max
131072
chen@ubuntu:~$ sysctl kernel.pid_max
kernel.pid_max = 131072
其实这个值是可以修改的,修改命令如下:
root@ubuntu:/home/chen# sysctl -w kernel.pid_max=4194304
kernel.pid_max = 4194304
但是内核也是有设置了一个硬上限的,我们来试试:
root@ubuntu:/home/chen# sysctl -w kernel.pid_max=4194305
sysctl: setting key "kernel.pid_max": Invalid argument
kernel.pid_max = 4194305
对于32位系统来说,系统进程个数硬上限是32768(32k)
对于64位系统来说,系统进程个数硬上限是4194304(4M)
9.2.4 特殊的pid
pid=0:调度进程,常常被称为交换进程。该进程是内核的一部分,它并不执行任何磁盘上的程序。
pid=1:通常是init进程,在自举过程结束时由内核调用。该进程的程序文件是/sbin/init。
pid=2:[kthreadd],不是很清楚名字,是内核线程的祖先。
以后知道了其他的,再加1了,现在就知道这些。
9.3 进程层次
为了管理进程,进程还有其他层次,进程组和会话。
9.3.1 进程组
进程组是一个或多个进程的集合。在一个进程组中,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。
可以通过下面函数来回去进程组ID:
#include <unistd.h>
pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
每个进程组都可以有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。
进程也可以通过函数来加入一个现有的组或者创建一个新进程组:
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
如果pid值为0:则表示要修改调用进程的进程组ID。
如果pgid为0,则由pid指定的进程ID将用做进程组ID。
这个函数有很多限制:
- pid参数必须指定为调用setpid函数的进程或其子进程,不能随意修改不相关进程的进程组ID。
- pid参数可以指定调用进程的子进程,但子进程如果已经执行了exec函数,则不能修改子进程的进程组ID。
- 在进程组间移动,调用进程,pid指定的进程及目标进程组必须在同一个会话之内。
- pid指定的进程,不能是会话首进程。
9.3.2 会话
会话是一个或多个进程组的集合。
linux系统提供的会话函数:
#include <unistd.h>
pid_t setsid(void);
pid_t getsid(pid_t pid);
我们来分析一个setsid:
如果这个函数的调用进程不是进程组组长,那么调用该函数会发生下面事情:
- 创建一个新会话,会话ID等于进程ID,调用进程称为会话的首进程。
- 创建一个进程组,进程组ID等于进程ID,调用进程成为进程组的组长。
- 该进程没有控制终端,如果调用setsid前,该进程有控制终端,这种联系就会断掉。
调用setsid函数的进程不能是进程组组长,否则调用失败。这样是因为如果出现的话,同一个进程组的进程分属不容的会话,所以不行。
说实话,将这些概念我也懵逼,不过我们看看具体的例子就清楚了一定,使用命令ps axjf
987 2305 2305 2305 ? -1 Ss 0 0:00 \_ sshd: root@pts/0
2305 2340 2340 2340 pts/0 2494 Ss 0 0:00 | \_ -bash
2340 2358 2358 2340 pts/0 2494 S 0 0:00 | \_ su chen
2358 2359 2359 2340 pts/0 2494 S 1000 0:00 | \_ bash
2359 2483 2483 2340 pts/0 2494 S 0 0:00 | \_ su
2483 2484 2484 2340 pts/0 2494 S 0 0:00 | \_ bash
2484 2494 2494 2340 pts/0 2494 R+ 0 0:00 | \_ ps axjf
987 2431 2431 2431 ? -1 Ss 0 0:00 \_ sshd: root@pts/1
2431 2460 2460 2460 pts/1 2481 Ss 0 0:00 \_ -bash
2460 2481 2481 2460 pts/1 2481 R+ 0 0:10 \_ ./test_file
2481 2482 2481 2460 pts/1 2481 R+ 0 0:10 \_ ./test_file
第一列是ppid,父进程id
第二列是 pid
第三列是 pgid,两个test_file就属于一个进程组
第四列是 sid,会话,所以在一个终端的进程都属于一个会话。
从这里了解到,bash执行一个程序,应该是修改过子进程的pgid。
9.3.3 控制终端
在linux系统中,用户通过终端登录系统后得到一个shell进程,这个终端成为shell进程的控制终端。
终端有两种,一种是终端设备(在终端登录情况下)一种是伪终端设备(网络登录的情况下)
这个也不是重点,要想区分,可以使用tty命令:
root@ubuntu:~/c_test# tty (伪终端设备)
/dev/pts/0
root@ubuntu:~/c_test# tty (终端设备)
/dev/tty1
会话和进程组还有一些其他特性:
- 一个会话可以有一个控制终端。这通常是终端设备或伪终端设备。
- 建立与控制终端连接的会话首进程被称为控制进程
- 一个会话中的几个进程组可被分成一个前台进程组以及一个或多个后台进程组
- 如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组。
- 无论何时键入终端的中断键和退出键,都会将信号发送至前台进程组的所有进程。
- 如果终端接口检测到网络已经断开,则将挂断信号发送至控制进程(会话首进程)
获取终端名字的函数:
#include <unistd.h>
char *ttyname(int fd);
功能:由文件描述符查出对应的文件名
参数:
fd:文件描述符
返回值:
成功:终端名
失败:NULL
这一篇感觉都是写一些概念,不过也没办法,进程的概念确实很多。