- 进程的概念
程序的每一次执行都会产生一个进程。
进程随着程序的运行而开始,随着程序的结束而结束。
每个进程都会分配0~3G的用户空间(内存)。3~4G的内核空间(内存)。进程是分配资源的最小单位。
用户空间可以有多份,内核空间只有一份。
进程和进程之间是相互独立的,比如每个进程都会有自己的文件描述符,都会有自己的缓冲区。
进程在内核中是一个task_struct结构体,在这个结构体中包含了进程相关的所有信息。
在系统上多个进程同时运行:时间片轮询,上下文切换。
2.进程和程序的区别
程序的每次执行会产生一个进程,进程的本质是存储在内存上的。
进程是有生命周期的,随着程序的启动而启动,随着程序的终止而终止,是动态变化的(进程终止不代表进程死亡)。是动态变化的。
程序是编译生成的二进制文件,存在硬盘上。
程序是静态的,没有生命周期。
3.进程的组成
task_struct 结构体,数据段,文本段。
4.进程的种类
交互进程:交互进程通常维护一个终端,我们用户可以通过终端和这个进程进行交互。
批处理进程:批处理进程是放在一个队列中,等待cpu运行。批处理进程的优先级较低。
gcc 编译程序就是批处理进程。
守护进程:
守护进程随着系统的启动而启动,随着系统的停止而停止。
比如我们windows上的各种服务就是守护进程。
5.进程的PID
在linux中每个进程都有一个PID,这个PID是进程在系统中的唯一标识。
系统中进程的最大数量:
特殊进程的pid:
0号进程:又叫做空闲进程,当系统上没有任何程序运行时,就运行0号进程。0号进程是用来启动1号进程和2号进程。启动完成后,0号进程结束。
1号进程:1号进程又叫做init进程。init进程的作用就是对系统进行各种初始化。初始化完成之后,init进程会一直存在。init进程会回收孤儿进程的资源。
2号进程:(kthreadd)进程,是调度进程。负责系统上各个进程的调度工作。
6.进程相关的命令
cpp
pidof a.out 查看有a.out运行产生的进程的pid
ps 查看进程的相关信息
ps -ef ------> 查看进程的pid,ppid
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10月10 ? 00:00:49 /sbin/init auto nopro
root 2 0 0 10月10 ? 00:00:00 [kthreadd]
root 3 2 0 10月10 ? 00:00:00 [rcu_gp]
root 4 2 0 10月10 ? 00:00:00 [rcu_par_gp]
UID:用户ID
PID:进程的PID
PPID:进程的父进程的PID
C:CPU占用率
STIME:进程的启动时间
TTY:是否和终端相关联
TIME:占用CPU的时间
CMD:启动进程的命令
ps -ajx //用来查看进程的状态
linux@kong:/proc$ ps -ajx
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:02 /sbin/init auto noprompt
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 I< 0 0:00 [rcu_gp]
PID:进程的PID
PPID:进程的父进程的PID
PGID:进程组ID,在终端上每次./a.out运行就会长生一个进程组
进程组分为前台进程组和后台进程组。前台进程组只能有一个
SID:会话ID,一个终端就是一个会话。
TTY:是否和终端相关联
TPGID:终端前台进程组ID
STAT:进程的UID
UID:进程的UID
TIME:占用的CPU的时间
sudo apt-get install htop
top/htop:动态查看进程的相关信息
进程号 USER PR NI VIRT RES SHR %CPU %MEM TIME+ COMMAND
47772 linux 20 0 2364 508 444 R 99.0 0.0 2:30.98 a.out
47173 linux 20 0 2364 576 512 R 89.5 0.0 104:45.86 a.out
PR:进程的优先级
NI:nice值 --- nice可以影响进程的优先级
nice:[-20~19] nice值越小,优先级越高
ps -eo pid,ni,cmd|grep a.out 查看a.out进程的pid,ni,cmd
sudo nice -n nice值(-20~19)./a.out
sudo renice nice值 进程的PID
killall a.ot 杀死所有的同名进程
kill -9 PID 杀死PID对应的进程
kill -19 PID 停止进程
kill -18 PID 继续运行进程
linux@kong:/proc$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
7.进程的状态
cpp
D 不可中断的休眠态(不可以被信号中断)
R 运行态
S 休眠态
T 停止态
t 被调试器停止的状态(比如使用GDB调试程序,停止的状态)
X 死亡态,无法看到
Z 僵尸态
< 高优先级
N 低优先级
L 在内存区锁定
s 是一个会话的组长
l 多线程
+ 前台进程
8.进程的状态切换实例
- 产生一个前台进程
- 使用ctrl + z使前台进程变成了 停止态的进程
- 使用kill -18 可以使停止态的进程变成运行态的进程(后台运行)
- 使用jobs -l(小写的L) 命令可以查看进程的作业号
- fg + 作业号 使后台进程变成前台进程
- bg + 作业号 使进程(停止态) 后台运行
9.进程创建原理
进程的创建是通过拷贝其父进程的资源完成的,子进程的所有资源都来自于他的父进程。
子进程创建之后,和父进程相互独立,互不影响。
10.进程创建的函数
cpp
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
参数:void
返回值:
成功:父进程返回子进程的PID,子进程返回0
失败:返回-1,置位错误码
11.进程创建的实例
11.1 进程创建的实例1(不关注返回值)
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
fork();
while(1);
return 0;
}
11.2 fork 函数的使用实例2(不关注返回值)
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
fork();//第一次fork调用完成,父进程产生一个子进程
fork();//第二次调用完成,父进程再产生一个子进程,
//第一次的子进程也产生一个子进程,目前共4个进程
while(1);
return 0;
}
11.3 fork()使用实例3(不关注返回值)
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
printf("#");
fork();//第一次fork调用完成,产生了两个进程,每个进程的缓冲区
//中,都有一个#
fork();//第二次fork,两个进程变成了四个进程,
//四个进程中每个进程的缓冲区都有一个#号
//终端输出多少个#号-----4个
return 0;
}
11.4 使用实例(不关注返回值)
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
for(int i = 0; i < 2; i++){
printf("#");
fork();
}
//输出多少#?
return 0;
}
fork n次会产生2的n次方个子进程
11.5 fork使用实例:关注返回值
getip()函数返回当前进程的pid
getppid()函数返回当前进程的父进程的pid
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
pid_t ret;
ret = fork();
if(-1 == ret){
PRINT_ERR("fork error");
}else if(0 == ret){
while(1){
printf("我是子进程,pid = %d,ppid = %d\n",\
getpid(),getppid());
sleep(1);
}
}else{
while(1){
printf("我是父进程,pid = %d,子pid = %d\n",\
getpid(),ret);
sleep(1);
}
}
return 0;
}
12.父子执行先后顺序问题
没有先后顺序,时间片轮询,上下文切换
13.父子进程内存空间问题
cpp
#include<my_head.h>
int main(int argc,const char *argv[]){
pid_t ret;
int a = 100;
ret = fork();
//子进程中 ret == 0
//父进程中 ret == 子进程的pid
if(-1 == ret){
PRINT_ERR("fork error");
}else if(0 == ret){
//子进程
while(1){
printf("我是子进程,a=%d\n",a);
a = 200;
printf("a修改后的数值是%d\n",a);
sleep(1);
}
}else{
//父进程
while(1){
printf("我是父进程,a=%d\n",a);
sleep(1);
}
}
return 0;
}
进程创建中的 写时 拷贝问题
copy-on-write
子进程在创建的时候,是拷贝父进程的资源产生的。
但是如果子进程不对拷贝过来的数据进行修改,那么本质上父子进程的数据段和代码段用的使同一块内存空间。
只有当子进程尝试对拷贝过来的数据进行修改时,才会真正的拷贝出一份数据的副本。
15.使用fork函数创建出三个进程。
cpp
#include <my_head.h>
int main(int argc, const char *argv[])
{
pid_t ret;
int a = 100;
ret = fork();
if (-1 == ret)
{
PRINT_ERR("fork error");
}
else if (0 == ret)
{
pid_t ret1;
ret1 = fork();
if (-1 == ret1)
{
PRINT_ERR("fork error");
}
else if (0 == ret1)
{
while (1)
{
printf("我是进程A,ipid = %d\n", getpid());
sleep(1);
}
}
else
{
while (1)
{
printf("我是进程B,pid = %d\n", getpid());
sleep(1);
}
}
}
else
{
while (1)
{
printf("我使进程C,pid = %d\n", getpid());
sleep(1);
}
}
return 0;
}
- 孤儿进程
父进程结束,子进程没结束,此时子进程就是孤儿进程。
孤儿进程会被init进程收养,由init进程为子进程回收资源。
注意:孤儿进程不是进程状态。
cpp
#include<my_head.h>
int main(int argc,const char *argv[]){
pid_t ret;
ret = fork();
//子进程中 ret == 0
//父进程中 ret == 子进程的pid
if(-1 == ret){
PRINT_ERR("fork error");
}else if(0 == ret){
//子进程
while(1){
printf("我是子进程,我还活着\n");
//此时子进程就变成了孤儿进程
sleep(1);
}
}else{
printf("我是父进程,我死了\n");
}
return 0;
}
17.僵尸进程
子进程结束后,父进程没结束,且父进程没有为子进程回收资源(调用wait函数),此时子进程就是僵尸进程。
僵尸进程会照成系统资源的浪费。
cpp
#include<my_head.h>
int main(int argc,const char *argv[]){
pid_t ret;
ret = fork();
//子进程中 ret == 0
//父进程中 ret == 子进程的pid
if(-1 == ret){
PRINT_ERR("fork error");
}else if(0 == ret){
//子进程
printf("我是子进程,我死了\n");
}else{
while(1){
printf("我是父进程,我还活着\n");
sleep(1);
}
}
return 0;
}
18.进程退出的函数 exit() _exit()
只有在main函数中调用return才会结束进程。
cpp
#include <unistd.h>
void _exit(int status);
功能:退出进程
参数:
status:进程退出的状态 (一般填写0~255的数字)
返回值:空
#include <stdlib.h>
void exit(int status);
功能:退出进程
参数:
status:进程退出的状态 (一般填写0~255的数字)
返回值:空
exit 退出会刷新缓冲区
_exit 退出不会刷新缓冲区
EXIT_SUCCESS:成功
EXIT_FAILURE:失败
exit代码示例
cpp
#include<my_head.h>
int test_fucn(){
//return 0;//退出函数,进程不会结束,下面的1111会输出
exit(0);//进程直接结束,1111111111不会输出
}
int main(int argc,const char *argv[]){
//test_fucn();
printf("111111111111");
//exit(0);//库函数,会刷新缓冲区,11111111会输出
_exit(EXIT_SUCCESS);//系统调用,不会刷新缓冲区,缓冲区中的数据丢失
return 0;
}