进程的概念
进程︰是程序执行时的一个实例
1.程序是被存储在磁盘上,包含机器指令和数据的文件
2.当这些指令和数据被装载到内存并被CPU所执行,即形成了进程。
3.一个程序可以被同时运行为多个进程
4.在Linux源码中通常将进程称为任务( task )
5.从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体
相关命令
pstree 以树状结构显示当前所有进程关系
ps 以简略方式显示当前用户拥有控制终端的进程信息,也可以适配以下选项
a 显示所有用户拥有控制终端的进程信息
x 也包括没有控制终端的进程
u 以详尽方式显示
w 以更大列宽显示
ps又被称为进程快照,是某一个时间点的
进程信息列表:
user :进程的用户ID
PID :进程ID
%CPU :CPU使用率
%MEM :内存使用率
VSZ :占用虚拟内存的大小(KB)
RSS :占用物理内存的大小
TTY :终端次设备号
STAT :进程状态
R 运行,即正在被处理器执行
s 可唤醒睡眠,系统中断、获得资源、收到信号,都可唤醒
D 不可唤醒睡眠,只能被wake_up系统调用唤醒
T 到SIGSTOP(19)信号进入暂停状态,收到SIGCONT(18)信号后继续运行。7-僵尸,已终止但其终止状态未被回收
< 高优先级
N 低优先级
L 存在被锁定的内存分页. s-会话首进程
| 多线程化进程
+ 在前台进程组中
START :进程开始时间
TIME :进程运行时间
COMMAMD:进程启动时间
top命令是实时改变的,类似任务管理器
父子孤尸
父子进程
Unix系统中的进程存在父子关系。一个父进程可以创建一到多个子进程,但每个子进程有且仅有一个父进程。整个系统中只有一个根进程,即PID为0的调度进程
孤儿进程
父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行如果父进程先于子进程终止,子进程即成为孤儿进程,同时被init进程收养,即成为init进程的子进程,因此init进程又被称为孤儿院进程
注意!
随着系统版本的升级,在较新的Linux系统中,1号进程为systemd,相比于以前版本的init而言,前者并行启动进程,减少系统启动等待时间。
在较新的linux系统中,孤儿院进程不再是1号进程
僵尸进程
1.父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行。如果子进程先于父进程终止,但由于某种原因,父进程并没有回收该子进程的终止状态,这时子进程即处于僵尸状态,被称为僵尸进程。
2.僵尸进程虽然已不再活动,即不会继续消耗处理机资源,但其所携带的进程终止状态会消耗内存资源。因此,作为程序的设计者,无论对子进程的终止状态是否感兴趣,都应该尽可能及时地回收子进程的僵尸。
进程标识
PID和PPID
1.每个进程都有一个非负整数形式的唯一编号,即PID(Process Identification,进程标识)
2.PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可以为其它进程所用,所以是不会立刻被重用的
3.进程的PID由系统内核根据延迟重用算法生成,以确保新进程的PID不同于最近终止进程的PID
4.系统中有些PID是专用的,比如
1.0号进程,调度进程,亦称交换进程(swapper),系统内核的一部分,所有进程的根进程,磁盘上没有它的可执行程序文件
2.1号进程,init进程,在系统自举过程结束时由调度进程创建,读写与系统有关的初始化文件,引导系统至一个特定状态,收养系统中的孤儿进程,以超级用户特权运行的普通进程,永不终止。
当然,新版本里1号进程变成了system,并且不具有孤儿院功能
3.除调度进程以外,系统中的每个进程都有唯一的父进程,对任何一个子进程而言,其父进程的PID即是它的PPID
相关函数
#include
pid_t getpid(void); //返回调用进程的PID
pid_t getppid(void); //返回调用进程的父进程PID
uid_t getuid(void); //返回调用进程的实际用户ID
gid_t getgid(void); //返回调用进程的实际组ID
uid_t geteuid(void); //返回调用进程的有效用户ID
gid_t getegid(void); //返回调用进程的有效组ID
pid_t 是整型int 的封装
//各种ID
#include<stdio.h>
#include<unistd.h>
int main(void){
printf("进程的PID: %d\n",getpid());
printf("父进程的PID:%d\n",getppid());
printf("实际用户ID :%d\n",getuid());
printf("实际组ID :%d\n",getgid());
printf("有效用户ID :%d\n",geteuid());
printf("有效组ID :%d\n",getegid());
return 0;
}
对于这个程序来说父进程其实就是bash
我们转换为root用户,并且重新运行,生成一个idroot(下图执行有错,但是后边改回来了)
root的有效ID则是 0
此时换回原用户,再看idroot
有效用户ID拥有设置用户ID位,取决于可执行程序拥有者的身份
进程的创建
#include
pid_t fork(void);
功能:创建调用进程的子进程
返回值:成功分别在父子进程中返回子进程的PID和0,失败返回-1
注意!!!该函数调用一次返回两次,在父进程中返回所创建子进程的PID,而在子进程中返回0,函数的调用者可以根据返回值的不同,分别为父子进程编写不同的分支。
系统中总的线程数达到了上限,或者用户的总进程数达到了上限,fork函数会返回失败。
线程上限:/proc/sys/kenel/threads-max
进程上限:ulimit -u
为父子进程设计相同的执行过程
int main(void){
....
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
..... \父子进程都执行的代码
return 0;
}
为父子进程设计先不同而后相同的执行过程
int main(void){
.......
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
...... 子进程执行的代码
}
else{
........ 父进程执行的代码
}
......... 父子进程都指向的代码
return 0;
}
为父子进程设计不同的执行过程
int main(void){
.......
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
...... 子进程执行的代码
}
else{
........ 父进程执行的代码
}
......... 父进程执行的代码
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我是父进程,我要创建子进程\n",getpid());
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
printf("%d进程:这是子进程\n",getpid());
return 0;
}
这是因为,当父进程,通过fork函数创建子进程后,父进程后续的程序,子进程也执行一次。
fork函数会给父进程返回一个值,存在父进程pid里,给父进程的pid是子进程的编号4075
也会给子进程一个值,存在子进程pid里,给子进程pid返回的是0
返回后,父子进程从if开始继续向下执行,都不是 -1,所以各自执行一遍,其实不确定哪个先执行
下为分支利用fork的返回值分开父进程和子进程各自要执行的代码,是同时执行,不分先后的
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我是父进程,我要创建子进程\n",getpid());
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:哈哈哈哈\n",getpid());
}else{
printf("%d进程:嘿嘿嘿嘿\n",getpid());
}
printf("%d进程:肚子有点饿\n",getpid());
return 0;
}
//子进程的创建
#include<stdio.h>
#include<unistd.h>
int main(void){
printf("%d进程:我是父进程,我要创建子进程\n",getpid());
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:哈哈哈哈\n",getpid());
return 0;
}
pid_t pid2 = fork();
if(pid2 == -1){
perror("fork");
return -1;
}
if(pid2 == 0){
printf("%d进程:动次打次\n",getpid());
return 0;
}
printf("%d进程:嘿嘿嘿嘿\n",getpid());
return 0;
}
首先父进程创建了子进程,子进程走到哈哈哈终结,然后父进程又创建一个子进程,子进程打印动次打次,父进程打印嘿嘿嘿嘿
思考:以下代码总共产生多少个进程
for(int i = 0; i < 3; i++){
fork();
}
答:一共8个进程
即2^n次方,n为创造次数
父子进程内存关系
由fork产生的子进程是其父进程的不完全副本,子进程在内存中的映射除了代码区与父进程共享同一块物理内存,其它各区映射到独立的物理内存,但其内容从父进程拷贝
//子进程是父进程的不完全副本
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int global = 10;//数据段
int main(void){
int local = 20;//栈区
int* heap = malloc(sizeof(int));
*heap = 30;//堆区
printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,global,&local,local,heap,*heap);
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){//子进程代码
printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,++global,&local,++local,heap,++*heap);
return 0;
}
//父进程代码
sleep(1);//保证子进程有足够时间可以执行完
printf("%d进程:%p:%d %p:%d %p:%d\n",getpid(),&global,global,&local,local,heap,*heap);
return 0;
}
顺便回顾一下