一、进程的概念
1.简介
进程描述的是一个程序的执行过程,当程序执行后,执行过程开始则进程产生,执行过程结束则进程也结束
进程一旦产生就需要分配相关资源,相应任务在cpu中执行,进程是资源分配的最小单位,是独立可调度的任务
2。进程与程序的区别
程序是静态的,是保存在磁盘上的指令集合,没有任何执行的概念
进程是动态的,他是程序执行的过程,包括了动态创建、调度、销毁的整个过程
3.并发与并行
并行:多个任务同时执行,依赖于物理的支持
并发:同一时间段有多个任务在同时执行(轮转时间片),将任务分成了许多个时间片,一个时间片的时间非常短,没有执行完的任务会等待下一次的分配
4.Linux进程管理
每个进程会与其他某个进程建立父子关系,对应进程叫做父进程
Linux系统会为每个进程分配id,id为当前进程的唯一标志,进程结束时会被回收
pid_t getpid(void)可获取当前进程id
pid_t getppid(void)可获取父进程id
二、进程的空间分配和堆栈大小
1、进程的空间分配
进程建立后系统会为进程分配相应空间,32为Linux系统会为每个进程分配4G虚拟空间,分为1G内核空间和3G用户空间
ulimit-s可查看进程的堆栈大小 ulimit-u可查看系统最大进程数
2、虚拟地址与物理地址
虚拟地址:不代表真实的内存空间,而是一个寻址编号
物理地址:真实存在的存储空间的编号
在操作系统中使用虚拟地址是因为:
(1)直接访问物理地址会导致地址没有隔离,容易导致内存被修改。
(2)通过虚拟地址空间可以实现每个进程空间都是独立的,操作系统会映射到不同的物理地址区间,在访问时互不干扰
3.三态模型:运行态、就绪态、等待态
五态模型:运行态、睡眠态(可中断睡眠,不可中断睡眠)、停止态、僵尸态
三、进程的相关命令
1、ps 显示当前进程的状态
ps -aux 显示所有进程的详细信息
ps -ef列出所有进程
2、top 实时显示进程信息
3、pstree将所有进程以树状结构方式展示
4、kill -9 进程号 终止进程
四、创建进程
1、pid_t fork()创建一个子进程 fork之后的语句会在子进程和父进程中同时运行
2、当子进程拷贝了父进程的文件描述符后,将会共享文件状态和文件偏移量等信息
3、在创建多个进程时,最主要的原则是由父进程统一创建,统一管理,不能进行递归创建
4、在进程结束时,需要释放分配给进程的地址空间以及内核中产生的各种数据结构,会自动释放缓冲区,自动调用exit函数
五、进程的退出
1、void exit(int status);
在系统中定义了两个状态值 : EXIT_SUCCESS 正常退出,EXIT_FAILURE 异常退出
exit在退出时会刷新缓冲区
2、void _exit(int status);
_exit 函数与 exit 函数功能相同,但_exit函数终止进程,但不会刷新缓冲区
3.exit 与 _exit的不同
_exit()属于系统调用,能够使进程停止运行,并释放空间以及销毁内核中的各种数据结构
exit()基于_exit()函数实现,属于库函数, 会自动刷新I/O缓冲区
六、进程的等待
在子进程运行结束后(调用exit或_exit),进程进入僵死状态,并释放资源,子进程在内核中的数据结构依然保留
父进程可通过wait()和waitpid() 等待子进程退出后,释放子进程遗留的资源
2、wait
pid_t wait(int *wstatus); wstatus:保存子进程退出状态值变量的指针
功能:会阻塞调用者进程(一般为父进程)
在子进程状态变为僵死态时,回收资源,并释放资源后返回
3、waitpid
pid_t waitpid(pid_t pid, int *wstatus, int options);
pid:进程id
-1 可以等待任意子进程
>0 等待id为pid的进程
wstatus:保存子进程退出状态值的指针
options:选项
WNOHANG 非阻塞选项
0 阻塞式与wait等同
waitpid函数的功能与wait函数一样,但比wait()函数功能更强大,可以理解成 wait() 底层调用waitpid()函数
用法:1、使用阻塞方式等待子进程退出 waitpid(-1,&status,0);
2、使用非阻塞的方式等待子进程退出 while((cpid=waitpid(-1,&status,WNOHANG))==0);
4、如果不关心状态值,子进程退出状态值的指针为NULL,即wait(NULL);、waitpid(-1,NULL,0);
七、进程替换
1、exec函数族
exec 函数的主要作用是用新的程序替换当前进程的内存空间,从而改变进程的执行内容。这意味着当前进程的代码段、数据段和栈等都会被新程序的相应部分替换
在 Linux 系统中提供了一组用于进程替换的函数,共有6个。
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char
*const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
参数说明
path:可执行文件的路径名
file:可执行文件名,可以通过PATH环境变量指定的路径
arg:参数列表,以NULL结尾
argv[]:参数数组
envp[]:环境变量数组
八、进程间的通信
进程间通讯方式有 管道,信号,消息队列,共享内存,网络
1、管道分为无名管道和有名管道,无名管道用于父子进程之间通讯,有名管道用于任意进程之间通讯
2、管道的本质是在内存建立一段缓冲区,由操作系统内核来负责创建与管理。
3、无名管道
无名管道属于单向通讯,只能用于父子进程
无名管道的发送端叫写端,接收端叫读端
无名管道将读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回将读端与写
端的文件描述符存入数组
创建无名管道:int pipe(int pipefd[2]);
pipefd:用于存储无名管道读端与写端的文件描述符的数组
pipefd[0]:读端文件描述符
pipefd[1]:写端文件描述符
若子进程为读端,父进程为写端,则通过pipefd和close关闭子进程写端,父进程的读端来实现父子进程信息的传递
管道的大小是有限的,不能让父/子进程同时对管道进行读/写操作
4、有名管道
有名管道是文件系统中可见的文件,但是不占用磁盘空间,仍然在内存中。可以通过 mkfifo命令创建有名管道(在共享目录不能使用mkfifo)
有名管道与无名管道一样,在应用层是基于文件接口进行操作
有名管道用于任意进程之间的通讯,当管道为空时,读进程会阻塞。
创建有名管道:int mkfifo(const char *pathname, mode_t mode);
如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式(只写,读写)
如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式(只读,读写)
通过open来实现以 只读/只写 的方式打开文件
有名管道的优缺点
优点:可以实现任意进程间通信,操作起来和文件操作一样
缺点:1.打开的时候需要读写一起进行否则就会阻塞,管道大小是 4096个字节
2.半双工的工作模式,如果和多个进程通信则需要创建多个管道
5、信号
信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。一般具有如下特点:
进程在运行过程中,随时可能被各种信号打断
进程可以忽略或者去调用相应的函数去处理信号
进程无法预测信号到达的精准时间
可以通过kill -l查看信号的种类
信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理 : 由内核进行投递给具体的进程并处理
在 Linux 中对信号的处理方式如下
忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作,Linux对每种信号都规定了默认操作
6、信号发送
当由进程来发送信号时,则可以调用 kill() 函数与 raise () 函数
int kill(pid_t pid, int sig);
int raise(int sig);
7、等待信号
在进程没有结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用 pause() 函数
int pause(void); 等待信号,让进程阻塞直到收到信号
pause函数一定要在收到信号之前调用,让进程进入到睡眠状态
8、信号处理
信号有操作系统内核发送给指定进程,进程收到信号后需要进行处理
有三种处理方式:忽略、默认、自定义
通过 signal 函数设置信号处理方式
sighandler_t signal(int signum, sighandler_t handler);
功能:设置信号的处理方式,如果是自定义处理方式,提供函数地址,注册到内核中
signum:信号编号
handler:信号处理方式
SIG_IGN---->忽略信号
SIG_DFL---->按照默认方式处理
自定义处理函数的地址
9、定时器信号
alarm可用于设置定时器
unsigned int alarm(unsigned int seconds);
定时器的定时任务由内核完成的,alarm 函数负责设置定时时间,并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号
10、子程序退出信号
在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号
11、消息队列
System V IPC 对象共有三种
消息队列
共享内存
信号量
System V IPC 是由内核维护的若干个对象,通过 ipcs 命名查询
每个 IPC 对象都有一个唯一的 键,可以通过 ftok() 函数生成(生成子进程为fork函数)
key_t ftok(const char *pathname, int proj_id);
每一个存在的文件都有一个id(inode节点号),通过ls -i查询
消息队列就是一个消息的列表,进程可以在消息队列中添加消息和的读取消息
消息队列具有FIFO的特性,具有无名管道与有名管道各自的优势,可以支持任意两个进程的进程间通讯
消息队列是属于 Sytem V IPC 的一种,由内核维护与管理 ,通过 ipcs -q 查看
创建消息队列:msgget
删除消息队列:msgctl
发送消息队列需要调用 msgsnd 函数
接收消息调用 msgrcv 函数
12、共享内存
在 Linux 系统中通过 ipcs -m 查看所有的共享内存
共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中,减少数据在内核空间缓存
创建共享内存调用 shmget() 函数
删除共享内存调用shmctl(函数
共享内存映射调用shmat函数 :将进程地址空间映射到共享内存上
解除共享映射调用 shmdt 函数