一.系统文件查看进程
- 进程的信息可以通过 /proc 系统文件夹查看
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。
二.通过命令查看进程
1.ps命令
- 显示此刻的进程
BSD选项
ps a #输出与终端相关进程信息
ps x #输出与终端无关进程信息
ps ax #输出所有进程信息
ps aux #以用户导向的格式输出所有进程信息
ps axuf #将进程以树形结构显示
打印结果:
[root@localhost ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.4 60604 7856 ? Ss Feb02 0:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
root 2 0.0 0.0 0 0 ? S Feb02 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S Feb02 0:00 [ksoftirqd/0]
root 5 0.0 0.0 0 0 ? S< Feb02 0:00 [kworker/0:0H]
root 7 0.0 0.0 0 0 ? S Feb02 0:00 [migration/0]
root 8 0.0 0.0 0 0 ? S Feb02 0:00 [rcu_bh]
root 9 0.0 0.0 0 0 ? S Feb02 0:00 [rcuob/0]
root 10 0.0 0.0 0 0 ? S Feb02 0:00 [rcuob/1]
USER //用户名称
PID //进程id
%CPU //进程的cpu占用比
%MEM //进程的内存占用比
VSZ //进程的虚拟内存占用比
RSS //常驻内存集大小
TTY //字符终端
STAT //进程状态
START //运行时间
TIME //占用CPU时间
COMMAND //进程名称
UNIX选项
ps -e #打印全部进程信息
ps -ef #以全信息格式打印全部进程信息
ps -efH #树形结构显示
ps -efo pid #查看某些信息
打印结果:
[root@localhost ~]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Feb02 ? 00:00:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 24
root 2 0 0 Feb02 ? 00:00:00 [kthreadd]
root 3 2 0 Feb02 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 Feb02 ? 00:00:00 [kworker/0:0H]
root 7 2 0 Feb02 ? 00:00:00 [migration/0]
root 8 2 0 Feb02 ? 00:00:00 [rcu_bh]
root 9 2 0 Feb02 ? 00:00:00 [rcuob/0]
root 10 2 0 Feb02 ? 00:00:00 [rcuob/1]
2.top指令
- 查看实时进程
- 常用指令
top -d 1 //指定刷新时间为1s
top -n 2 //刷新两次结束
k pid enter //终止某一进程
u username enter //查看指定用户进程
Tasks: 29 total 进程总数
1 running 正在运行的进程数
28 sleeping 睡眠的进程数
0 stopped 停止的进程数
0 zombie 僵尸进程数
Cpu(s): 0.3% us 用户空间占用CPU百分比
1.0% sy 内核空间占用CPU百分比
0.0% ni 用户进程空间内改变过优先级的进程占用CPU百分比
98.7% id 空闲CPU百分比
0.0% wa 等待输入输出的CPU时间百分比
0.0% hi
0.0% si
Mem: 191272k total 物理内存总量
173656k used 使用的物理内存总量
17616k free 空闲内存总量
22052k buffers 用作内核缓存的内存量
Swap: 192772k total 交换区总量
0k used 使用的交换区总量
192772k free 空闲交换区总量
123988k cached 缓冲的交换区总量。
3.pidof命令
- 查看某一进程进程号
[root@localhost ~]# pidof bash
12680 966
4.pstree命令
- 以进程树形式显示
三.通过系统调用获取进程标示符
1.getpid()函数以及getppid()函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
四.通过系统调用创建进程-fork初识
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
详悉fork()函数
- 从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
-
返回值:
-
子进程中返回0
-
父进程返回子进程id
-
出错返回-1
-
fork函数被调用一次将返回两次,在子进程中返回0,在父进程中返回子进程的ID。
子进程获得父进程的数据空间、堆、栈副本
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/time.h>
#include <malloc.h>
int globvar=6;//全局变量
char buf[]="hello world\r\n";
int main( )
{
int var;//栈上变量
pid_t pid;
var = 88;
int *ptr=(int *)malloc(sizeof(int));
*ptr=2;
if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)
{
printf("write error\r\n");
return -1;
}
printf("before fork\r\n");
if((pid=fork())<0)
{
printf("fork error");
return -1;
}
else if(pid==0)//child
{
++*ptr;
++var;
++globvar;
}
else//parent
{
sleep(2);
}
printf("pid = %ld, globvar = %d, &var = %ld , var = %d , *ptr = %d , ptr=%ld\r\n",(long)getpid(),globvar,(long)&var, var ,*ptr,(long)ptr);
free(ptr);
return 0;
}
运行结果:
hello world
before fork
pid = 15694, globvar = 7, &var = 140737488348208 , var = 89 , *ptr = 3 , ptr=6299664
pid = 15690, globvar = 6, &var = 140737488348208 , var = 88 , *ptr = 2 , ptr=6299664
我们看到地址都是一样的,但是值不一样,说明子进程中发生了拷贝,但是为什么地址一样呢?
这里涉及到物理地址和逻辑地址(或称虚拟地址)的概念。
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同, 但其对应的物理空间是同一个。
当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,
由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。
fork之后内核会通过将子进程放在队列的前面,以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。
fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。
每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同。
写时拷贝
- 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。(类似C++String类中的写时拷贝)
fork常规用法
- 父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
五.进程状态
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列
里。 - S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))。 - D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的
进程通常会等待IO的结束。 - T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
以通过发送 SIGCONT 信号让进程继续运行。 - X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
1.僵死状态
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
#include <stdio.h>
#include <stdlib.h>
int main() {
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){
parent printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
僵死进程的危害
- 进程的退出状态必须被维持下去,父进程如果一直不读取,那子进程就一直处于Z状态?。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
说,Z状态一直不退出,PCB一直都要维护。 - 如果一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构
对象本身就要占用内存。 - 内存泄漏
2.孤儿进程
- 父进程先退出,子进程就称之为“孤儿进程”
- 孤儿进程被1号init进程领养,由init进程回收。
在这里插入代码片