什么是进程、线程?

程序是放在磁盘上的指令和数据的有序集合(文件),静态的,存放于磁盘
进程是执行程序时所分配的资源的总称,是程序的一次执行过程,动态的,包括创建、调度、执行和消亡

进程和程序包含模块

202412292316745

进程包括:代码段、数据段、BSS段、堆、栈、进程控制块
程序包括:代码块、数据段、BSS段

BSS段

  • 存放程序中未初始化的全局变量的一块内存区域,英文全称(Block Started by Symbol)

数据段

  • 数据段存放程序中已初始化的全局变量,以及static声明的变量

代码段

  • 存放程序执行代码的内存区域,可能包含只读的常数变量,如字符串常量

堆(heap)

  • 用于存放进程中被动态分配的内存段,程序调用malloc等函数分配内存时,新分配的内存存放在堆中(堆被扩张)
  • 当调用free函数释放内存时,被释放的内存会从堆中去除(堆的缩减)

栈(stack)

  • 又称堆栈,存放程序临时创建的局部变量(不包括static声明的变量)
  • 在函数被调用时,函数的参数和函数的返回值也会入栈
  • 栈方便用来保存和恢复现场,堆栈可被看做一个寄存器、临时交换数据的内存区。

进程控制块(PCB)

  • 进程控制块包含:进程标识PID、进程用户、进程状态、优先级、文件描述符(最多1024个)

进程类型(三种)

交互进程:在shell下启动,可在前台,也可在后台
批处理进程:和终端无关,被提交到作业队列中顺序执行
守护进程:和终端无关,一直在后台运行

进程状态(四种)

运行态:程序正在运行或准备运行
等待态:进程在等待一个事件的发生或某种系统资源,可中断或不可中断
停止态:进程被中止,收到信号后可继续运行
死亡态(僵尸态):已终止的进程,但PCB没有被释放

进程常用命令

ps:查看系统进程快照

  • -e:显示所有进程
  • -l:长格式显示更详细的信息
  • -f:全部列出,通常和其他选项
  • 不加参数时只显示当前shell终端的进程
  • ps -eLf 显示系统中所有进程及其每个线程的信息,每个线程占一行。
  • ps -elf 显示系统中所有进程的详细信息,每个进程占一行,不包含线程信息。

进程状态含义

表头 含义
F 进程标志,说明进程的权限,常见标志:
1:进程可复制,不能被执行;
4:进程使用超级用户权限
S 进程状态,常见状态:
-D:不可唤醒的睡眠状态,通常用于I/O情况
-R:该进程正在运行
-S:该进程处于睡眠状态,可被唤醒
-T:停止状态,可能在后台暂停或处于除错状态
-W:内存交互状态
-X:死掉的进程
-Z:僵尸进程,程序已中止,但部分程序还在内存中
-<:高优先级(BSD格式中出现)
-N:低优先级
-L:锁入内存状态
-s:包含子进程
-l:多线程
-+:位于后台运行
UID 运行进程的用户ID
PID 进程的ID
PPID 父进程的ID
C 该程序占用的CPU,单位为百分比
PRI 进程优先级,数值越小,优先级越高
NI 进程优先级
ADDR 该进程位于内存的哪个位置
SZ 该进程占用的内存大小
WCHAN 进程是否在运行,”-“代表正在运行
TTY 进程由哪个终端产生
TIME 进程占用CPU的运算时间
CMD 产生该进程的命令名

其他查看进程信息命令

  • top:查看进程的动态信息
  • /proc:查看进程的详细信息

nice:按用户指定的优先级运行进程

  • nice [-n NI值]:值的范围(-20~19),数值越大优先级越低
  • 普通用户只能调整范围(0~19),且只能调整自己的进程,只能调高,不能调低
  • root用户可设置负值,且能调整任何用户的进程

renice:重新指定程序优先级

1
renice [优先级] PID

前后台进程切换

1
2
3
4
jobs        # 查看后台进程
# x为使用jobs查看时显示的后台进程序号
bg $x # 将挂起的进程在后台运行
fg $x # 将后台运行的程序放到前台运行

子进程

子进程概念

子进程为由另一个进程(称为父进程)所创建的进程,子进程的创建会复制一份父进程的代码,但并不是执行所有代码

子进程的创建

fork函数

用于创建新的进程,在unistd.h头文件中被定义

1
pid_t fork(void);
  • 返回值:创建成功父进程返回子进程的进程号,子进程返回0,失败时返回-1
  • 可通过fork的返回值判断父进程和子进程,子进程只执行fork函数后的代码
  • 父进程和子进程的执行顺序由系统调度

父子进程之间的关系

  • 子进程继承了父进程的内容,父进程和子进程都有独立的地址空间互不影响
  • 父进程先结束时,子进程会变成孤儿进程,被 init 进程收养,子进程变成后台进程
  • 子进程先结束时,父进程不及时回收,子进程会变成僵尸进程

进程的结束

exit函数、_exit函数

exit和_exit函数都是结束进程的函数

1
2
void exit(int status);
void _exit(int status);
  • exit在头文件stdio.h中被定义,_exit函数在头文件unistd.h中被定义
  • exit在结束进程时会刷新流缓冲区,_exit不会刷新流缓冲区
  • return和exit的区别是:main函数结束会隐式调用exit函数,普通函数return是返回上一级

进程的回收

wait函数

用于进程的回收,在头文件sys/wait.h中被定义

1
pid_t wait(int *status);
  • 成功时返回回收的子进程的进程号,失败时返回EOF
  • 子进程未结束时,父进程一直阻塞
  • 有多个子进程时,哪个先结束,哪个就回收
  • status指定保存子进程返回值和结束方式的地址
  • status为NULL时表示直接释放子进程PCB,不接受返回值

waitpid函数

用于进程回收,在头文件sys/wait.h中被定义

1
pid_t waitpid(pid_t pid, int *status, int option);
  • 返回值:成功时返回回收的子进程PID或0,失败时返回EOF
  • pid用于指定回收哪个子进程或任意子进程
  • status指定用于保存子进程的返回值和结束方式的地址
  • option指定回收方式,0或WNOHANG,目前Linux中只支持WNOHANG和WUNTRACED两个选项
    • WNOHANG:若由pid的指定子程序没有结束,则waitpid()不阻塞,立即返回0
    • WUNTRACED:返回终止子程序信息和因信号停止的子程序信息
pid参数 说明
pid > 0 只等待进程ID等于pid的子进程,指定的子进程不结束,会一直等待
pid = 0 等待同一个进程组中的任何子进程,若子进程加入别的进程组时,会不做理睬
pid = -1 等待任何一个子进程退出
pid < -1 等待一个指定进程组中的任何子进程,进程组的ID等于pid的绝对值

子进程通过exit、_exit、return返回某个值(0-255)时,父进程调用wait(&status)进行回收,返回的status可使用以下宏获取对应的值

宏名称 说明
WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 判断子进程的返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 获取结束子进程的信号类型

进程退出和进程回收的区别

  • 进程退出:是进程生命周期的结束,指进程主动终止并进入结束状态(僵尸状态)。退出时操作系统会进行清理和资源释放,但进程信息仍保留在系统中,直到被回收。
  • 进程回收:是进程退出后的父进程或操作系统彻底清除该进程所有资源的过程。只有父进程通过获取退出状态,操作系统才会从进程表中删除该进程,释放所有资源,进程才算被完全回收。

进程—exec函数族

使用exec函数族能实现让父子进程执行不同的程序,父进程创建子进程后,子进程调用exec函数,父进程不受影响

  • 进程调用exec函数可执行某个程序
  • 进程当前内容会被指定的程序替换

execl函数和execlp函数

用于进程执行程序,在unistd.h头文件中被定义

1
2
3
4
5
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
// 例子
execl("/bin/ls", "ls", "-la", NULL);
execl("ls", "ls", "-la", NULL);
  • 返回值:成功时则执行指定程序,失败时返回EOF
  • path:执行程序名称,包括路径
  • file:执行的程序名称,在PATH中查找
  • arg, …, NULL:传递给程序的参数列表,最后一个参数要为NULL

execv函数和execvp函数

用于进程执行程序,在unistd.h头文件中被定义

1
2
3
4
5
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
// 例子
char *argv[] = {"ls", "-la", NULL};
execv("/bin/ls", argv);
  • 返回值:成功时执行指定程序,失败时返回EOF
  • arg:封装成指针数组的形式,最后一个参数写NULL

进程—system函数

1
2
3
int system(const char *command);
// 例子
system("ls -la /home/ubuntu");
  • 返回值:成功时返回命令command的返回值,失败时返回EOF
  • 当前进程等待command执行结束后才能进行执行

守护进程

守护进程是Linux的三种进程类型之一的一种Linux后台服务进程,其生命周期较长,通常独立于控制终端且周期性地执行某种任务或等待处理某些发生的事件。

特点

  • 后台运行、独立于任何终端、周期性执行或等待处理某些特定的事件。

为什么要独立于终端

  • 为了避免进程被任何终端产生的信息所打断,其执行过程的信息不在任何终端上显示。

相关概念

  • 进程组(Process Group):进程集合,每个进程都有个组长,其进程ID就是该进程的ID
  • 会话(Session):进程组集合,每个回话有一个组长,其进程ID为该会话的组ID
  • 控制终端(Controlling Terminal):每个会话可以有一个独立的控制终端,与控制终端连接的Leader就是控制进程。

创建守护进程命令和函数

nohup命令

1
nohub xxx &

setsid函数

1
pid_t setsid(void);
  • 返回值:返回调用进程的会话ID,失败时返回-1,设置errno
  • 调用了该函数的进程,既是新的会长、也是新的组长

getsid函数

1
pid_t getsid(pid_t pid);
  • 返回值:成功时返回调用进程的会话ID,失败时返回-1,设置errno
  • pid为0时表示察看当前进程session ID
  • ps ajx命令查看系统中的进程,参数a列出当前用户和其他用户的进程,参数j列出与作业控制相关的信息,参数x列出有终端和无终端的进程
  • 组长进程不能成为新会话的首进程,新会话首进程必定成为组长进程

chdir函数

改变进程的工作目录

1
chdir(const char *s);

创建守护进程的5个标准步骤

  • 父进程创建子进程,父进程退出,使用函数fork()创建子进程,父进程使用exit(0)退出
  • 子进程创建新会话,成为新会话组长,使用setsid()创建
  • 改变守护进程的工作目录,使用函数chdir(path)改变进程工作目录
  • 重新设置文件的权限掩码,设置为0,只影响当前进程,使用函数umask()函数设置
  • 关闭文件描述符,关闭所有从父进程继承的打开文件,使用close关闭stdin、stdout、stderr,描述符分别是0,1,2。