进程的基本概念和相关命令
1.进程的基本概念
进程:是程序动态执行的过程,包括创建、调度、消亡。
程序:存放在外存中的一段数据的集合。
2.进程的创建
2.1 进程空间分布
进程空间由用户空间和内核空间(不允许用户访问)构成,每个进程运行起来后,操作系统开辟 0 - 4G虚拟内存空间。用户空间就是文本段(文本区) + 数据段(数据区) + 系统数据段(堆区、栈区)。
2.2 虚拟地址和物理地址
1. 虚拟地址: 所有能够被用户看到的地址均为虚拟地址,表示用户可以寻址的范围
2. 物理地址: 内存存放数据对应的实际硬件物理地址
3. MMU: 实际地址和物理地址之间的映射由内存映射单元(MMU)完成

2.3 多进程存储
各个进程虚拟空间一致,物理空间不同
2.4多进程的调度
常见的进程调度算法有1)先来先执行,后来后执行。2)高优先级调度算法。3)时间片轮转调度算法,多进程调度本质是宏观并行,微观串行。宏观:一个CPU同时执行多个进程任务。微观:一个CPU在多个任务中高速切换保障多任务执行。(好比一个厨子同时做10道菜,宏观来看他是在同时进行,但微观层面他只能在同一时刻做一道菜)
3.进程的相关命令
3.1top
示例:top
功能:根据CPU占用率和内存占用率查看当前所有进程的信息
PID:唯一区分进程的ID号
USER:创建者
PR NI:优先级
VIRT
RES
SHR:资源占用
S:状态
%CPU:CPU占用率
%MEM:内存占用率
TIME+:运行的时长
COMMAND:进程命令
q退出
3.2ps -ef
示例:ps -ef
功能:查看该时刻的所有进程信息
UID:创建进程的用户
PID:进程的ID号
PPID:父进程的ID号
C
STIME
TTY:进程依赖的终端
TIME
CMD
ps -ef | grep 进程名
查找与进程名对应的进程信息
3.3ps -aux
示例:ps -aux
功能:查看该时刻的所有进程信息
USER
PID
%CPU
%MEM
VSZ
RSS
TTY
STAT:进程的状态
START TIME
COMMAND
3.4后台执行进程任务
示例:./a.out &
功能:后台执行a.out进程
3.5jobs
示例:jobs
功能:查看终端下所有的后台进程任务
3.6fg
示例:fg 编号(通过jobs查看)
功能:后台执行的任务放到前台执行
3.7nice/renice
示例:nice -n 优先级编号 进程名
renice -n 优先级编号 进程PID
功能:改变进程的优先级
优先级范围:-20 - 20
数字越小优先级越高
3.8kill/killall
示例:kill -编号 进程PID
killall -编号 进程名
功能:杀死进程任务
kill -9 PID
4.进程的状态
| 进程状态 | 标识 | 含义 |
| 就绪态/运行态 | R | 进程位于CPU任务调度的队列中 |
| 可唤醒等待态 | S | 等待某个资源的到来,资源到来后被唤醒加入调度队列 等待过程中可以被打断 |
| 不可唤醒等待态 | D | 等待某个资源的到来,资源到来后被唤醒加入调度队列 等待过程中不可以被打断 |
| 停止态 | T | 用户人为让进程任务暂停等待 |
| 僵尸态 | Z | 进程代码运行结束,空间尚未回收 |
5.进程相关的函数接口
5.1fork创建一个进程
原型:pid_t fork(void);
功能:
创建一个新的进程,新的进程称为子进程,调用fork的进程称为父进程
参数:
缺省
返回值:
父进程中返回子进程的PID
子进程中返回0
出错返回-1
fork()就像细胞分裂:
1)调用 fork()时,当前进程(父进程)会复制一个几乎完全一样的子进程。子进程和父进程的代码、变量、打开的文件等几乎完全相同(但独立运行)
2)fork()的返回值不同:
父进程:返回子进程的PID(一个正数)。
子进程:返回 0(表示自己是子进程)。
出错:返回 -1(比如内存不足,无法创建子进程)。
3)fork()之后,父子进程同时运行,谁先执行取决于操作系统的调度。
一个父进程创建一个子进程:
#include "head.h"
int main(void)
{
pid_t pid; // 定义一个变量 pid,用于存储 fork() 的返回值
pid = fork(); // 调用 fork() 创建子进程
// fork() 会复制当前进程,生成一个几乎完全相同的子进程
// 注意:fork() 调用一次,但返回两次(父进程和子进程各返回一次)
if (-1 == pid) // 如果 fork() 失败(比如系统资源不足)
{
perror("fail to fork"); // 打印错误信息
return -1; // 退出程序
}
if (0 == pid) // 如果 fork() 返回 0,说明当前是子进程
{
// 子进程会执行这里的代码
printf("我是子进程, PID:%d PPID:%d\n", getpid(), getppid());
// getpid() 获取当前进程ID(子进程自己的PID)
// getppid() 获取父进程ID
}
else if (pid > 0) // 如果 fork() 返回一个正数,说明当前是父进程
{
// 父进程会执行这里的代码
// pid 的值就是子进程的PID
printf("我是父进程, PID:%d childpid:%d\n", getpid(), pid);
}
// 注意:fork() 之后,父子进程都会继续执行后面的代码!
printf("hello world\n"); // 父子进程都会打印这句话
while (1) // 让父子进程都进入死循环(防止程序退出,方便观察)
{
// 空循环,不做任何事情
}
return 0; // 由于 while(1),实际不会执行到这里
}
一个父进程创建两个子进程:
#include "head.h"
int main (void)//一父俩子
{
pid_t pid1;
pid_t pid2;
pid1 = fork();
if(-1 == pid1)
{
perror("fail to fork");
return -1;
}
if(0 == pid1)
{
printf("我是子进程1, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid1 > 0)
{
pid2 = fork();
if(-1 == pid2)
{
perror("fail to fork");
return -1;
}
if(0 == pid2)
{
printf("我是子进程2, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid2 > 0)
{
printf("我是父进程, 我的PID:%d 我儿子childpid1:%d childpid2:%d\n", getpid(),pid1, pid2);
}
}
while (1)
{
/* code */
}
return 0;
}
需要注意的是,子进程会把父进程缓存区也拷贝过去,因此父子进程都会打印hello world。
#include "head.h"
int main(void)
{
pid_t pid;
printf("hello world");
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
printf("我是子进程\n");
}
if (pid > 0)
{
printf("我是父进程\n");
}
return 0;
}
知识扩展之写时拷贝
写时拷贝(Copy-0n-Write,COW)是一种高效的资源管理技术,核心思想是"只有在真正需要修改时才进行拷贝"。
5.2getpid和getppid
具体功能已在fork展示
原型:pid_t getpid(void);
功能:获得调用函数进程的PID号
原型:pid_t getppid(void);
功能:获得调用函数进程的父进程的PID号
5.3exit与_exit
原型:void exit(int status);
功能:
结束进程任务并返回进程结束状态
参数:
status:进程结束状态的值
原型:void _exit(int status);
功能:
结束进程任务并返回进程结束状态
参数:
status:进程结束状态的值
#include "../head.h"
int main(void)
{
pid_t pid;
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
while (1)
{
printf("PID:%d PPID:%d\n", getpid(), getppid());
sleep(1);
}
}
else if (pid > 0)
{
printf("父进程即将退出!");
_exit(0);
}
return 0;
}
需要注意:在主函数中调用exit和return功能保持一致, return在函数内部将结束该函数 ,exit在函数内部会将进程结束 ,exit会在结束前刷新缓存区 _exit不会刷新缓存区。
5.4wait
原型:pid_t wait(int *wstatus);
功能:
回收子进程空间
参数:
wstatus:存放子进程结束状态空间的首地址
返回值:
成功返回回收到的子进程的PID
失败返回-1
WIFEXITED(wstatus):测试进程是否正常结束
WEXITSTATUS(wstatus):获得进程退出的值
WIFSIGNALED(wstatus):测试进程是否被杀死
WTERMSIG(wstatus):获得杀死该进程的信号编号
wait具有阻塞等待功能,等到有子进程结束才会回收子进程继续向下执行 wait可以实现父子进程任务的同步。
#include "../head.h"
int main(void)
{
pid_t pid;
pid_t ret;
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
sleep(5);
printf("子进程即将退出!\n");
exit(0);
}
else if (pid > 0)
{
ret = wait(NULL);
printf("回收到子进程(PID:%d)空间\n", ret);
while (1)
{
}
}
return 0;
}
5.5waitpid
原型:pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:
回收指定的子进程空间
参数:
pid:要回收的进程的PID(-1表示回收任意子进程)
wstatus:存放子进程结束状态空间首地址
options:
0
阻塞回收
WNOHANG 非阻塞回收
返回值:
成功返回回收到的子进程PID
失败返回-1
指定的子进程空间没结束,返回0
waitpid(-1, NULL, 0) 等价于 wait(NULL)
6.进程消亡
6.1孤儿进程
父进程先结束,子进程会成为孤儿进程,被init进程收养
用上面的一个父进程两个子进程举例:这里不加while循环,父进程先于子进程结束。子进程2会被init接管,其父进程pid会变成1:
#include "head.h"
int main (void)//一父俩子
{
pid_t pid1;
pid_t pid2;
pid1 = fork();
if(-1 == pid1)
{
perror("fail to fork");
return -1;
}
if(0 == pid1)
{
printf("我是子进程1, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid1 > 0)
{
pid2 = fork();
if(-1 == pid2)
{
perror("fail to fork");
return -1;
}
if(0 == pid2)
{
printf("我是子进程2, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid2 > 0)
{
printf("我是父进程, 我的PID:%d 我儿子childpid1:%d childpid2:%d\n", getpid(),pid1, pid2);
}
}
// while (1)
//{
/* code */
//}
return 0;
}

加上while循环保证父进程不会结束,这样子进程2的父进程pid不会变为1
#include "head.h"
int main (void)//一父俩子
{
pid_t pid1;
pid_t pid2;
pid1 = fork();
if(-1 == pid1)
{
perror("fail to fork");
return -1;
}
if(0 == pid1)
{
printf("我是子进程1, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid1 > 0)
{
pid2 = fork();
if(-1 == pid2)
{
perror("fail to fork");
return -1;
}
if(0 == pid2)
{
printf("我是子进程2, PID:%d PPID:%d\n", getpid(), getppid());
}
else if (pid2 > 0)
{
printf("我是父进程, 我的PID:%d 我儿子childpid1:%d childpid2:%d\n", getpid(),pid1, pid2);
}
}
while (1)
{
/* code */
}
return 0;
}
6.2僵尸进程
僵尸进程是每个进程结束必然会经历的阶段,子进程结束后,父进程没有回收子进程空间,导致进程执行结束,空间依然被占用的状 态,称为僵尸进程,子进程结束,父进程回收子进程空间,即可避免产生僵尸进程,或者父进程先结束,子进程会成为孤儿进程,孤儿进程被init进程收养,子进程再结束,init 进程回收进程空间。
7.exec函数族
7.1exec函数族
利用进程空间执行另一份代码,且不会返回当前代码,只有exec报错
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL
*/);
int execlp(const char *file, const char *arg, .../* (char *) NULL
*/);
int execle(const char *path, const char *arg, .../*, (char *) NULL,
char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const
envp[]);
l:参数以列表的形式传递
p:在系统变量PATH对应的目录下查找文件
v:参数以指针数组的形式传递
e:执行新代码是更新环境变量
通过exec函数在main.c中调用hello


exec常搭配fork使用,fork负责创建新的子进程,exec负责让子进程执行自己的代码
演示在mysystem部分。
7.2主函数传参
用法在exec函数部分已经展示过了
void main(void);
int main(void);
int main(int argc, const char *argv[]);
int main(int argc, const char **argv);
argc:传入参数的个数
argv:存放每一个传入参数指针的数组名
argv[0] = "./a.out"
argv[1] = "hello"
argv[2] = "world"
argv[3] = "how"
argv[...] = ...
argv[argc] = ...
7.3system函数(执行命令并返回)
原型:int system(const char *command);
功能:
运行command命令
参数:
command:shell命令字符串首地址
返回值:
成功返回0
失败返回-1
用exec搭配fork写一个自己的system函数:
#include "head.h"
void mysystem(void)
{
pid_t pid;
pid = fork();
if(-1 == pid)
{
perror("fail to fork");
return;
}
if (0 == pid)
{
execlp( "ls","ls", "-l", NULL);
}
wait(NULL);
return;
}
int main(void)
{
printf("system上面\n");
mysystem();
printf("system下面\n");
return 0;
}
更多推荐




所有评论(0)