进程Process
1.什么是进程
1.1. 概念
程序: 编译好的可执行文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念
进程: 一个独立的可调度的任务
执行一个程序所分配资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
1.2. 特点
- 系统会为每个进程分配0-4g的虚拟空间,其中0-3g是用户空间,每个进程独有;3g-4g是内核空间,所有进程共享

- 轮转调度:时间片,系统为每个进程分配时间片(几毫秒~几十毫秒),当一个进程时间片用完时,CPU调度另一个进程,从而实现进程调度的切换 (没有外界干预是随机调度)
1.3. 进程段
Linux中的进程大致包含三个段:
数据段 : 存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
正文段: 存放的是程序中的代码
堆栈段 : 存放的是函数的返回地址、函数的参数以及程序中的局部变量 (类比内存的栈区)
1.4. 进程分类
交互进程: 该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程: 该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。(目前接触不到)
守护进程: 该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
1.5. 进程状态
1)运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。
2)睡眠态(等待态):
可中断睡眠态(TASK_INTERRUPTIBLE)S:处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
(只能通过特定的函数进行唤醒,是不能随便去中断的)
不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:该状态的进程只能用wake_up()函数唤醒。
3)暂停态(TASK_STOPPED):T
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
4)死亡态:进程结束 X
5)僵尸态:Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
< 高优先级
N 低优先级
s 会话组组长
l 多线程
- 前台进程
1.6. 进程状态切换图
进程创建后,进程进入就绪态 ,当CPU调度到此进程时进入运行态 ,当时间片用完时,此进程会进入就绪态 ,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞 态,完成IO操作(阻塞结束)后又可进入就绪态 ,等待CPU的调度,当进程运行结束即进入结束态


1.7. 进程相关命令
ps 查看系统中的进程 -aux -ef
top 动态显示系统的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
kill 发送信号给进程
jobs查看当前终端的后台进程
bg 将进程切换到后台执行
fg 将进程切换到前台执行
2.进程函数
2.1. 创建进程 fork()
#include <sys/types.h>`
`#include <unistd.h>`
`pid_t` `fork(void);`
`功能:创建子进程`
`参数:无`
`返回值:`
` 成功:在父进程中,返回子进程的进程号 >` `0`
` 在子进程中,返回值为0`
` 失败:-1 并设置errno`
`
#include <stdio.h>`
`#include <sys/types.h>`
`#include <unistd.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`pid_t pid;`
` pid =` `fork();`
`if(pid <` `0)`
`{`
`perror("fork err");`
`return` `-1;`
`}`
`// 当返回值为0,相当于在子进程中运行`
`else` `if(pid ==` `0)`
`{`
`printf("in then child\n");`
`// while(1);`
`}`
`// 当返回值大于零的时候相当于在父进程中运行`
`else`
`{`
`printf("in the parent\n");`
`}`
`return` `0;`
`}`
`
解释:./a.out会启动一个进程,执行到fork()函数时会在当前进程中创造了一个子进程并把代码以及数据信息拷贝到子进程,这两个进程只有个别数据例如进程号不一样,此时这两个进程由CPU随机调度。注意!!子进程会得到fork函数返回值然后执行fork之后的代码,fork函数之前的代码不会执行。
特点
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。

2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
#include <stdio.h>`
`#include <sys/types.h>`
`#include <unistd.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`pid_t pid;`
`int num =` `10;`
` pid =` `fork();`
`if(pid <` `0)`
`{`
`perror("fork err");`
`return` `-1;`
`}`
`// 当返回值为0,相当于在子进程中运行`
`else` `if(pid ==` `0)`
`{`
` num++;`
`printf("in then child %d\n", num);`
`}`
`// 当返回值大于零的时候相当于在父进程中运行`
`else`
`{`
`sleep(2);`
`printf("in the parent %d\n", num);`
`}`
`while(1);`
`return` `0;`
`}`
`
- fork之前的代码会被复制但是不会被重新执行一遍,fork之后的代码会被复制,并且父子进程分别执行一遍
- fork之前打开的文件,fork之后会拿到同一个文件描述符,操作同一个文件指针
- 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
6)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
#include <stdio.h>`
`#include <sys/types.h>`
`#include <unistd.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`pid_t pid;`
` pid =` `fork();`
`if(pid <` `0)`
`{`
`perror("fork err");`
`return` `-1;`
`}`
`// 当返回值为0,相当于在子进程中运行`
`else` `if(pid ==` `0)`
`{`
`printf("in then child %d\n", pid);`
`}`
`// 当返回值大于零的时候相当于在父进程中运行`
`else`
`{`
`printf("in the parent %d\n", pid);`
`while(1);`
`}`
`return` `0;`
`}`
`

2.2. 回收资源
#include <sys/types.h>`
`#include <sys/wait.h>`
`pid_t` `wait(int` `*wstatus);`
`功能:回收子进程资源(阻塞)`
`参数:wstatus:子进程退出状态,不接受子进程状态设为NULL`
`返回值:成功:回收的子进程的进程号`
` 失败:-1`
`pid_t` `waitpid(pid_t pid,` `int` `*wstatus,` `int options);`
`功能:回收子进程资源`
`参数:`
` pid:>` `0 指定子进程进程号`
`==` `-1 任意子进程`
`==` `0 等待其组ID等于调用进程的组ID的任一子进程`
`<` `-1 等待其组ID等于pid的绝对值的任一子进程`
` wstatus:子进程退出状态`
` options:0 阻塞`
` WNOHANG:非阻塞 (没有子进程退出立刻返回)`
` 返回值:正常:回收的子进程的进程号`
` 当使用选项WNOHANG且没有子进程结束时:0`
` 失败:-1`
`
#include <stdio.h>`
`#include <unistd.h>`
`#include <sys/types.h>`
`#include <sys/wait.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`pid_t pid;`
` pid =` `fork();`
`if(pid <` `0)`
`{`
`perror("fork err");`
`return` `-1;`
`}`
`// 当返回值为0,相当于在子进程中运行`
`else` `if(pid ==` `0)`
`{`
`sleep(2);` `// 让子进程等待一会结束`
`printf("in then child %d\n", pid);`
`}`
`// 当返回值大于零的时候相当于在父进程中运行`
`else`
`{`
`printf("in the parent %d\n", pid);`
`// wait(NULL); // 回收子进程资源`
`// 0: 阻塞`
`// WNOHANG:非阻塞,有可能调用的时候,子进程还没有结束回收不到资源还是会产生僵尸`
`// 需要轮询`
`// waitpid(-1, NULL, 0); `
`while(1)`
`{`
`if(waitpid(-1,` `NULL, WNOHANG)` `>` `0)`
`break;`
`}`
`}`
`return` `0;`
`}`
`
2.3. 结束进程
#include <stdlib.h>`
`void` `exit(int status);`
`功能:结束进程,刷新缓存`
`#include <unistd.h>`
`void` `_exit(int status);`
`功能:结束进程,不刷新缓存`
`参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。`
` 通常0表示正常结束;`
` 其他数值表示出现了错误`
`
#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`int` `fun()`
`{`
`printf("hello");`
`// exit(0); // 刷新缓存区`
`// _exit(0); // 不刷新缓存区`
`// 返回函数调用位置,继续向下执行代码`
`return` `0;`
`}`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`fun();`
`while(1);`
`return` `0;`
`}`
`
补充:
exit和return区别:
exit:不管在子函数还是主函数,都可以结束进程
return:当子函数中有return时返回到函数调用位置,并不结束进程
2.4. 获取进程号
#include <sys/types.h>`
`#include <unistd.h>`
`pid_t` `getpid(void);`
`功能:获取当前进程的进程号`
`pid_t` `getppid(void);`
`功能:获取当前进程的父进程号`
`
#include <stdio.h>`
`#include <sys/types.h>`
`#include <unistd.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`pid_t pid;`
` pid =` `fork();`
`if(pid <` `0)`
`{`
`perror("fork err");`
`return` `-1;`
`}`
`// 当返回值为0,相当于在子进程中运行`
`else` `if(pid ==` `0)`
`{`
`sleep(2);`
`printf("in then child %d %d\n",` `getpid(),` `getppid());`
`}`
`// 当返回值大于零的时候相当于在父进程中运行`
`else`
`{`
`printf("in the parent %d %d\n", pid,` `getpid());`
`}`
`return` `0;`
`}`
`两者出现不一致的情况是,父进程结束了,子进程成为了孤儿进程`
`