1. 概念
1.1. 程序:
- 编译好的可执行文件./a.out
- 存放在磁盘上的指令和数据的有序集合(文件)
- 静态的,没有执行的概念
1.2. 进程:
- 进程是程序的一次执行过程。
- 进程是动态的,包含创建、调度、执行、消亡。
- 进程是执行一个程序分配资源的总称。
- 独立的可调度的任务
2. 进程 特点
- 系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。
- CPU调度进程时 会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作
3. 进程 段
Linux中的 进程包含三个段:
- “数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
- “正文段”存放的是程序中的代码
- “堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
4. 进程 分类
交互进程:
该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:
该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:
该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
5. 进程状态
- 运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。 - 睡眠态(等待态):
● 可中断睡眠态(TASK_INTERRUPTIBLE)S:
○ 处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
● 不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:
○ 该状态的进程只能用wake_up()函数唤醒。 - 暂停态(TASK_STOPPED): T
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。 - 死亡态:
进程结束 X - 僵尸态(TASK_ZOMBIE):Z
当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
6. 进程状态转换图
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
7. 函数实现
fork exit _exit wait waipid getpid getppid
7.1. 创建进程 fork
pid_t fork(void);
功能:创建子进程
返回值:
成功:
在父进程中:返回子进程的进程号 >0
在子进程中:返回值为0
失败:
-1并设置errno
7.1.1. 特点:
- 子进程几乎拷贝了父进程的全部内容。
-
- 包括代码、数据、系统数据段中的pc值、 栈中的数据、父进程中打开的文件等;
- 但它们的PID、PPID是不同的。
- 父子进程 有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
- 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
- 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
- fork函数之前的代码会被复制,但是不会被再执行一遍,fork之后的代码会被复制,并且父子进程分别执行一遍。
- fork之前打开的文件,fork之后拿到的文件描述符,操作的是同一个文件指针。
(不同的进程打开相同的文件,操作的是不同的文件指针,虽然是指向同个文件,但是各操作各的)
- fork函数创建父子进程执行顺序不一定。
-
- vfork:先执行完子进程,再执行父进程
7.2. 回收进程 wait/waitpid
pid_t wait(int *status);
功能:回收 任一 已退出的子进程的资源 (阻塞)(回收僵尸进程的资源)
参数:
status:子进程退出状态,不接受子进程状态设为NULL
返回值:
成功:回收的子进程的进程号
失败:-1
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
pid:>0 等待进程号为pid的子进程。
=-1 等待任一个子进程。与wait等效
=0 等待 进程组号 与 目前进程组号 相同 的任何子进程,
也就是说 和 任何 调用waitpid函数的进程 在同一个进程组的子进程
<-1 等待 进程组号 为 pid绝对值 的任何子进程
status:子进程退出状态
options:
0:阻塞,即wait
WNOHANG:非阻塞:
如果 pid指定的子进程没有结束,则waitpid函数立即返回0,而不是阻塞在这个函数上等待;
如果 已经结束了,则返回该子进程的进程号。
WUNTRACED:如果子进程进入暂停状态,则马上返回
返回值:
正常:
结束的 子进程的 进程号
当使用选项WNOHANG且没有子进程结束时:0 并且父进程不等待
出错:
-1,并且将失败的原因存放在errno变量中
wait(NULL); //不关心状态,阻塞等待任一子进程退出
waitpid(-1, NULL, 0); //和wait等价;不关心状态,阻塞回收任一子进程
waitpid(pid, NULL, 0); //不关心状态,阻塞回收指定pid的子进程
waitpid(-1, NULL, WNOHANG); //不关心状态,不阻塞 试探回收 任意子进程
7.3. 退出进程 exit _exit
void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
return 与exit的区别:
- return:关键字,当子函数中有return时返回到函数调用位置,并不结束进程(函数的退出)
- exit:函数,不管在子函数还是主函数,都可以结束进程(进程的退出)
GPT回答 子进程回收问题(重要)
在Linux的C语言中,子进程终止后,它占用的一部分资源会被系统回收。这包括子进程的内存空间、打开的文件描述符、进程状态等。
系统会自动回收子进程的资源,但它不会自动清理子进程的终止状态。为了避免僵尸进程的出现,父进程通常需要调用wait()、waitpid()或类似的系统调用来等待子进程的终止并回收子进程的终止状态。
父进程在调用这些等待函数时,会被阻塞,直到其中一个子进程终止。当父进程成功回收该子进程的终止状态后,子进程会被完全销毁,释放所有资源,并且父进程可以获取到子进程的终止状态。
如果父进程没有等待子进程的终止,或者一直没有调用相应的回收函数,那么子进程就会一直处于僵尸状态,占用系统的进程表项资源,直到父进程退出或显式地回收子进程。
所以,尽管操作系统会在子进程终止时回收一些资源,但父进程仍然需要调用相应的函数来回收子进程的终止状态,以避免僵尸进程的产生。
GPT:子进程main的return问题
在Linux C编程中,子进程的main函数不会直接返回给任何特定的实体。(简单说,无所谓)
7.4. 获取进程号 getpid/getppid
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
- getpid 获取 当前进程的 进程号
- getppid 获取 当前进程的 父进程的 进程号
- 在父进程中,fork返回值 即子进程的进程号
8. 守护进程
8.1. 特点
- 守护进程是后台进程;
- 生命周期比较长,从系统启动时开启,系统关闭时结束;
- 它是脱离控制终端且周期执行的进程。
8.2. 步骤
- 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
- 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
- 改变进程运行路径为根目录
原因:进程运行的路径不能被删除或卸载;chdir("/")
- 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
- 关闭文件描述符
将不需要的文件关闭;close()
创建流程:
代码练习:
创建一个守护进程,循环间隔1s向文件中写入一串字符“hello”
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// 创建子进程,父进程退出
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程 作为守护进程
{
setsid(); //在子进程中创建新会话,让子进程作为会话组组长
chdir("/"); //改变运行路径为根目录
umask(0); //重设文件权限掩码 (保证权限最大,都和1&)
close(0); //关闭用不到的文件描述符
close(1);
// 业务逻辑
int fd = open("/tmp/info.log", O_CREAT | O_TRUNC | O_WRONLY, 0777); //这个权限最大,因为umask为0
if (fd < 0)
{
perror("open err");
return -1;
}
while (1)
{
write(fd, "hello\n", 6);
sleep(1);
}
}
else //父进程退出。由init系统回收资源
exit(0);
return 0;
}