进程

1.程序
数据结构 + 算法 = 程序
程序是数据和指令的集合
程序是存放在磁盘中的可执行文件

2.程序执行方式
1.顺序执行
缺点:cpu利用率非常低

2.并发执行
20世纪60年初,MIT(麻省理工)和IBM首先提出进程的概念

3.进程
1.进程是具有独立功能的程序关于某个数据集合上的一次运行活动
2.一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源

本质:
进程是一个程序的一次执行过程,同时也是资源分配的最小单位。

4.进程与程序的区别:
1.程序是静态的(指令的集合),没有执行的概念
2.进程是动态的概念(是程序执行的过程,包抱了动态创建,高度,消亡的整个过程)

3.进程一个独立运行的活动单位,是程序执行和资源管理的最小单位
(独立:如果系统中某个进程崩溃,它不影响其它进程,可以通过由内核控制的机制相互通信)

4.一个程序可以对应多个进程

5.进程控制块(PCB)
Linux系统如何去描述进程呢?
在Linux中,进程由一个叫做task_struct的结构体描述,该结构体记录了进程中的一切。

vim /usr/src/linux-headers-3.13.0-24/include/linux/sched.h
1200行左右

6.ps命令
ps用于查看当前终端启动的进程
PID:进程的编号
TTY:命令所运行的终端
TIME:占用CPU的时间
CMD:进程的名称及路径

ps -aux | more 用于分屏显示所有的进程信息
使用回车翻一行,空格翻一页,滚轮上下翻动
按q退出
USER -用户名称
PID -进程号
%CPU -占用CPU的百分比
%MEM -占用内存的百分比
VSZ -虚拟内存的大小
RSS -物理内在的大小
TTY -终端号
STAT -状态信息
START-启动时间
TIME:占用CPU的时间
COMMAND:进程的名称

ps -ef
常见的进程状态:
S --休眠状态
s --进程的领导者,它拥有子进程
Z --僵尸进程,进程已结束,但是资源没有回收
R --正在运行的进程
O --就绪状态
T --挂起状态
< --优先级较高的进程
N --优先级较低的进程
l --多线程的
+ --进程属于前台进程
....

目前主流的操作系统支持多进程,如果进程A启动了进程B
那么A就叫做父进程,B称为A的子进程

7.Linux进程的启动次序:
进程0(系统进程)负责启动进程1(init进程)和2,其它所有的进程都
是由进程1和2 直接或间接启动的,从而形成树形结构。

8.PID进程号
Linux用进程ID管理进程,每个进程都有一个唯一的进程ID,在同一时刻
进程的PID是不会重复的,进程的PID可以延时重用。
PID 的本质就是一个整数。
怎么获取PID以及PPID?
linux中获取当前进程的PID和PPID的系统调用函数为:
pid_t getpid(void);
pid_t getppid(void);

9.进程运行状态
进程是程序的执行过程,根据它的生命周期,可以划分三种状态
1.执行态:该进程正在执行,即正占用CPU
2.就绪态:进程具备执行的一切条件,正等待分配CPU的处理时间片
3.等待态:进程不能使用CPU,若等待的事件发生,则可将其唤醒。

10.Linux进程地址空间布局
程序结构:
一个可执行程序包含三个部分:
代码段:主要存放指令以及只读的(常量)数据(如字符串常量)。
数据段:全局或静态的已初始化的变量
BSS段:全局或静态的未初始化的变量。BSS段会在main()执行之前自动清0

在执行程序时,OS首先在内核中创建一个进程,为这个进程申请一个PCB,用于管理整个进程的资源。
其中mm_struct成员用来管理与当前进程相关的所有内存资源。

进程结构:
在32位平台下,一个进程拥有4G虚拟内存地址空间:
1.代码段,数据段,BSS段,这三个部分直接从磁盘拷贝到内存
2.堆:通常在堆中进行动态内存分配,malloc系列
3.mmap映射区:
4.栈:保存局部变量(包括函数参数),内存的分配和回收都自动进行

11.linux的虚拟内存地址空间
在Linux中,内存地址其实是一个虚拟内存地址,不是物理内存的真实地址
每个进程一启动,就先天赋予了0-4G的虚拟内存地址,虚拟内存地址本质是一个整数,这个整数通过内存映射对应一个物理内存地址,但先天不对应任何的物理内存。

虚拟内存地址自身存储不了任何数据,只有内存映射后才能存储数据,否则 引发段错误。
程序员所接触到的内存地址都是虚拟内存地址。

0-3G是给用户使用的,叫用户空间
3-4G是给系统内核使用的,叫内核空间
用户空间的程序不能直接访问内核空间,但可以通过由内核提供的系统函数进入内核空间。


12.Linux进程管理相关API
1.进程创建
#include <unistd.h>
pid_t fork(void);
功能:用来创建一个新进程,新创建的进程,我们称之为子进程(child process),调用fork()的进程 ,称为父进程(parent process).
返回值:
失败: 返回-1
成功: 父进程返回子进程的PID
子进程返回0

注:
1.fork创建子进程后,父子进程谁先运行不确定,不同的系统有不同的算法,谁先结束也不确定
2.使用fork创建子进程后,父子进程的执行方式是:
a.对于fork之前的代码,由父进程执行一次
b.对于fork之后的代码,由父子进程各运行一次(运行2次)
c.fork函数的返回值也是由父子进程各自返回一次

3.fork是通过复制父进程来创建子进程的,会复制除了代码区之外的所有的内存区域,代码区会共享。

4.父子进程的关系
fork()之后,父子进程同时运行
如果子进程先结束,子进程给父进程发一个信号,父进程负责回收子进程的资源
如果父进程先结束,子进程变成了孤儿进程,会认进程1(init)做新的父亲,init进程也叫孤儿院。

如果子进程在发信号时,出现了问题,或父进程没有及时处理信号,子进程就会变成僵尸进程。

ctrl + alt + F1/2/3/4/5/6

5.扩展练习:
如何创建3个新进程,实现4个进程?

如何创建2个新进程,实现3个进程?

2.进程终止
进程结束可能是正常结束,也可能是非正常结束
正常结束:
1.在main函数中调用return 0;
2.调用exit()/_exit()

非正常结束:
在外力干涉下结束 ctrl + c
操作系统帮你结束

#include <stdlib.h>
void exit(int status);
--------------------
#include <unistd.h>
void _exit(int status);
-------------------
#include <stdlib.h>
void _Exit(int status);
功能:exit()和_exit()都是用来终止进程的。

exit()和_exit()的不同:
exit()不会立即结束进程,可以调用atexit()注册过的函数之后再结束
会检查文件的打开情况,把文件缓冲区中的内容写回文件,也就是清理IO缓冲
_exit()立即结束进程


#include <stdlib.h>

int atexit(void (*function)(void));
功能:用于注册参数指定的函数,该函数会进程正常终止时被调用
正常终止:exit()调用 / 在main函数中执行return


return / exit():
return是用来返回函数
exit()是用来退出进程的

在main函数中,return 0与这个exit(0)效果是一样的。

3.进程等待
exit(1000) --->如何去拿退出码?
退出码是给父进程的,必须保证子进程先结束,父进程才能拿到退出码
函数wait()和waitpid()用于让父进程等待子进程结束,并取得子进程的退出码

pid_t wait(int *status);
功能:主要用于挂起当前正在运行的进程,直到有一个子进程终止为止
成功返回终止进程的PID,失败返回-1
当参数不为空时,会将获取到的状态信息存放到参数指定的int类型空间中,通过以下宏来获取相关信息:
WIFEXITED(status) --当进程正常终止时,返回true
WEXITSTATUS(status) --获取子进程的退出状态信息。


pid_t waitpid(pid_t pid, int *status, int options);
功能:主要用于按照指定的方式等待指定的进程状态发生改变
pid:等待的进程号
<-1 等待进程组ID为PID的绝对值的任意子进程(了解)
-1 等待任意一个子进程结束(重点)
0 等待和当前进程在同个进程组的任意子进程(了解)
>0 等待进程号为PID的进程(重点)

status:指针类型,获取 进程的退出状态信息
options:
0代表阻塞(一般给0即可)
WNOHANG代表非阻塞
注:
如果options用了WNOHANG,返回有三种:
正数:等待到了结束的子进程的PID
0:没有子进程结束,直接返回
-1:出错了

wait和waitpid的区别:
wait是等待任意一个子进程结束,等待子进程,父进程必然阻塞
waitpid可以等待指定的子进程结束(也可以任意),等待过程中父进程可以阻塞也可以不阻塞

wait(&status) == waitpid(-1,&status,0);

3.进程替换
exec函数族
#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., 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[]);

exec函数族提供了一个在进程中启动另一个进程执行的方法
它可以根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的,代码段,数据段和堆栈段,在执行完成 之后,原调用进程的内容除了进程号之外,其它全部被新的进程替换了。
exec函数族的细微差别:
前4位 统一是exec
第5位 l:参数传递为逐个列举方式 execl,execlp,execle
v:参数传递为构造指针数组方式 execv,execvp,execve

第6位 e:可传递新进程环境变量 execvpe,execle
p:可执行文件查找方式为文件名 execlp,execvp


作业:
1.假设下面这个程序编译后名字是main,请问这个程序执行后,
系统总共出现过多少个main进程?
int main()
{
fork();
fork() && fork() || fork();
fork();
}

2.写一个程序,创建一个子进程来播放一个目录下的mp3文件
实现循环播放


#include<stdio.h>
#include<unistd.h>

int main()
{
if(fork() == 0)//子进程
{//调用execl(),这里给出ps程序所在的完整路径
if(execl("/bin/ps","ps","-ef",NULL) < 0)
{
perror("execl");
}
}

}

#include<stdio.h>
#include<unistd.h>

int main(int argc,char* argv[],char*env[])
{
char *arg[] = {"ps","-ef",NULL};
int i = 0;
while(1)
{
printf("%s ",env[i]);
i++;
if(env[i] == NULL)
{
break;
}
}
if(fork() == 0)//子进程
{//调用execv(),这里给出ps程序所在的完整路径
if(execv("/bin/ps",arg) < 0)
{
perror("execv");
}
}

}

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

void myexit(void)
{
printf("我就是那个被注册的函数,exit退出时会调用我 ");
}
int main()
{
atexit(myexit);
printf("exit using ");

printf("hello wrold");


exit(0);
// _exit(0);

}

#include<stdio.h>
#include<unistd.h>
int main()
{
int a = 200;
pid_t pid;
pid = fork();
//从这里开始以下代码,父子进程各运行一次
if(pid > 0)
{
a++;
printf("I am parent process ");
printf("a = %d ",a);//201

}
else if(pid == 0)
{
a--;
printf("I am child process ");
printf("a = %d ",a);//199
}
printf("a = %d ",a);//201,200,199
return 0;
}

#include<stdio.h>
#include<unistd.h>

int main()
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
printf("我是子fork出来的子进程,我的PID = %d ",getpid());
printf("我的父进程的PID = %d ",getppid());
}
else if(pid > 0)
{
printf("我是父进程,我的PID = %d ",getpid());
printf("我的子进程的PID = %d ",pid);

}


}

#include<stdio.h>
#include<unistd.h>
int main()
{
printf("当前进程的PID:%d ",getpid());
printf("当前进程的父进程的PID:%d ",getppid());
while(1)
{
sleep(1);
}

}

#include<stdio.h>

int a = 100;
int b;
int main()
{
int c;
int arr[100];
static int d = 200;
char *p = "hello";
char *p1 = malloc(100);
printf("12345 ");

return 0;
}

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
//1.使用fork创建子进程
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork");
exit(-1);
}
//2.子进程开始运行
if(pid == 0)
{
printf("子进%d程开始运行 ",getpid());
sleep(5);
printf("子进程结束 ");
exit(100);
}
//3.让父进程等待子进程结束
printf("父进程开始等... ");
int status;
pid_t pid1;
pid1 = wait(&status);
if(pid1 == -1)
{
perror("wait");
exit(-1);

原文地址:https://www.cnblogs.com/liudehao/p/5750873.html