1 进程概念
概念:进程是程序执行时的一个实例。
-程序时被存储在磁盘上,包含机器指令和数据的文件。
-当这些指令和数据被装载到内存并被CPU所执行,即形成了进程。
-一个程序可以被同时运行为多个进程。
-在Linux源码中通常将进程称为任务(task)。
-从内核观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的实体/实例。
2 相关命令
2.1 pstree
pstree(process tree)以树状结构显示当前所有进程关系
2.2 ps 进程快照
ps命令以简略方式显示当前用户拥有控制终端的进程信息,可配合以下选项:
-a 显示所有用户拥有控制终端的进程信息
-x 也包括没有控制终端的进程
-u 以详尽方式显示
-w 以更大列宽显示
进程信息列表:
user 进程的用户ID
PID 进程ID
%CPU CPU使用率
%MEM 内存使用率
VSZ 占用虚拟内存的大小(KB)
RSS 占用物理内存的大小(KB)
TTY 终端设备号
STAT 进程状态(推荐以下文章) Linux进程状态(ps stat)详解_smartvxworks的博客-CSDN博客)
R 运行,即正在被处理器执行
S 可唤醒睡眠(阻塞状态、sleep()等),系统中断,获得资源,收到信号,都可唤醒
D 不可唤醒睡眠,职能被wake_up系统调用唤醒
T 收到SIGSTOP(19)信号进入暂停状态,收到SIGCONT(18)信号后继续运行
Z 僵尸,已终止但其终止状态未被回收
< 高优先级
N 低优先级
L 存在被锁定的内存分页
s 会话首进程
l (小写L)多线程化进程
+ 在前台进程组中
START 进程开始时间
TIME 进程运行时间
COMMOND进程启动命令
3 父子孤尸
Unix系统中的进程存在父子关系。
一个父进程可以创建一到多个子进程,但每个子进程有且仅有一个父进程。
整个系统中只有一个根进程,即PID为0的调度进程:
父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行。
如果父进程先于子进程终止,子进程即成为孤儿进程,同时被某个专门的进程收养,即成为该进程的子进程,因此该进程又被称为孤儿院进程。旧的ubuntu系统中,1号进程担当孤儿院进程。
父进程创建子进程后,子进程在操作系统的调度下与其父进程(几乎)同时运行。如果子进程先于父进程终止,但由于某种原因,父进程并没有回收该子进程的终止状态,这是子进程即处于僵尸状态,称为僵尸进程。(父进程结束后,操作系统会回收父、子的僵尸)
僵尸进程虽然已不再活动,即不会继续消耗处理器资源,但其所携带的进程终止状态会消耗内存资源。因此,作为程序设计者,无论对子进程的终止状态是否感兴趣,都应尽可能及时地回收子进程的僵尸。
4 进程标识(PID)
每个进程都有一个非负整数形式的唯一编号,即PID(Process Identification,进程标识)。
PID在任何时刻都是唯一的,但可以重用:当进程终止并被回收后,PID可为其他进程所用。
进程的PID由系统内核根据延迟重用(一直65535,再从小的未占用PID开始)算法生成,以确保新进程的PID不同于最近终止的进程PID。(fd是最小未占用原则)。
系统中有些PID是专用的,比如:
-0号进程,即调度进程、交换进程(swapper),系统内核的一部分,所有进程的根进程,磁盘上没有它的可执行文件。
-1号进程(systemd,执行pstree可查看),init进程,在系统自举过程结束时由调度进程创建,读写与系统有关的初始化文件,引导系统至一个特定状态,以超级用户特权运行的普通进程,永不终止。旧的ubuntu系统中,1号进程担当孤儿院进程。
unix系统只有0号进程和1号进程是固定的。
除调度进程以外,系统中的每个进程都有唯一的父进程,对任何一个子进程而言,其父进程的PID即是它的PPID。
相关函数:
#include <unistd.h>
pid_t getpid(void); // 返回调用进程的PID(pid_t 就是 int)
pid_t getppid(void); // 返回调用进程的父进程的PID
uid_t getuid(void); // 返回调用进程的实际用户ID
uid_t geteuid(void); // 返回调用进程的有效用户ID
gid_t getgid(void); // 返回调用进程的实际组ID
gid_t getegid(void); // 返回调用进程的有效组ID
5 创建子进程
5.1 fork()
#include <unistd.h> //子进程代码块 勿忘 return 0;
pid_t fork(void);
功能:创建调用进程的子进程
返回值:成功分别在父子进程中返回子进程的PID和0,失败返回-1
注意:该函数调用1次返回2次,在父进程中返回所创建子进程的PID,在子进程中返回0。函数的调用者可以根据返回值的不同,分别为父子进程编写不同的处理分支。
系统中总的线程数达到了上限,或者用户的总进程数达到了上限,fork函数会返回失败。
fork()之后的代码是啥,父子进程就都执行啥。
//创建子进程
#include<stdio.h>
#include<unistd.h> //fork()
int main(void){
printf("%d进程:我是父进程,我要创建子进程了\n",getpid());
//创建子进程
pid_t a = fork();
if(a == -1){
perror("fork");
return -1;
}
//子进程代码
if(a == 0){
printf("%d进程:我是子进程\n",getpid());
return 0;// !!! 重要 , 关键!!!!!
}
//父进程代码
printf("%d进程:我是父进程\n",getpid());
return 0;
}
//编译执行即可
5.2 父子进程之间的关系
由fork()产生的子进程是其父进程的不完全副本,子进程在内存中的映像除了代码区(只读常量区)与父进程共享同一块物理内存,其他各区映射到独立的物理内存,但其内容从父进程拷贝,子进程修改自己的变量,不影响父进程的变量(下述代码)。
子进程变量地址与父进程变量地址相同(执行下述代码可得),是虚拟地址相同,物理地址是不同滴(下图可知,都叫5栋105,不一定是同个小区)。
可以简单认为子进程就是父进程的副本,因为代码区本来就不能改,只读常量区嘛。
//copu.c 子进程是父进程的副本
#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;
}
//编译执行
子进程复制文件描述符表,但共用(而非复制)文件表项:
fork()函数返回后,系统内核会将父进程维护的文件描述符表也复制到子进程的进程表项中,但并不复制文件表项:
//ftab.c 子进程复制父进程的文件描述符表,但共用文件表项
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
//父进程打开文件
int fd = open("./ftab.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//父进程向文件写入数据 hello world!
char* buf = "hello world!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
//父进程创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码,调整文件读写位置
//以验证子进程复制了父进程的文件描述符表,但共用同一个文件表项
if(pid == 0){
if(lseek(fd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
close(fd);
return 0;
}
//父进程代码,再次写入数据 linux!
sleep(1);
buf = "linux!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
return 0;
}
//编译执行,查看ftab.txt
上述代码中,子进程复制了父进程的fd,那么同一个文件表项关联了2个fd。只有这2个fd都close(),文件表项才释放掉。所以子进程和父进程代码都要close(fd)。
6 考试题
for(i=0; i++; i<3);{
fork();
}
循环结束后,一共8个进程,7个子进程。思路如下。
此题简单,子进程代码未出现return 0;
第一次循环,父进程得到子进程。2。
父子共进第二次循环,得到各自子进程。4。
4进程共同进入第三次循环,得到各自子进程。8。