一、进程相关概念
1.什么是进程
程序:静态的,编译好的可执行文件,存放在磁盘中的指令和数据的集合
进程:动态的,是程序的一次执行过程,是独立的可调度的任务
2.进程的特点
(1)对32位系统,系统会为每个进程分配0~4G的虚拟空间,其中,0~3G(用户空间)是每个进程独有的,3~4G(内核空间)是所有进程共有
进程间通信:通过内核空间
(2) CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作。(没有外界干预的情况下怎么调度进程是CPU随机分配的 )(3)进程控制块task_struct(了解)
● 进程控制块pcb:包含描述进程的相关信息● 进程标识PID:唯一的标识一个进程
主要进程标识:
进程号(PID: Process Identity Number)
父进程号:(Parent Process ID: PPID)
● 进程用户
● 进程状态、优先级
● 文件描述符(记录当前进程打开的文件)
3.进程段
Linux中的进程大致包含三个段:
数据段 : 存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
正文段: 存放的是程序中的代码
堆栈段 : 存放的是函数的返回地址、函数的参数以及程序中的局部变量 (类比内存的栈区)
4.进程分类
交互进程: 该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。 该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类 进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程: 该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
(使用top命令查看进程信息时,tty字段为?的进程)
5.进程状态

6. 进程状态切换

7.进程相关命令

补充:根据进程的优先级进行调度,优先级高的进程先执行。
两种类型:
非剥夺式(非抢占式)优先级调度算法。当一个进程正在处理上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在进行的进程继续运行,直到由于其自身原因而主动让出处理机(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。
剥夺式(抢占式)优先级调度算法。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
二、进程函数接口
1.创建进程 fork()
cpp
#include <sys/types.h>
#include <unistd.h>
/*
功能:创建子进程
参数:无
返回值: 成功:父进程-->返回子进程进程号
子进程-->返回0
失败:父进程-->返回-1,并设置errno
子进程并未创建
*/
pid_t fork(void);

fork函数特点:
1)子进程几乎拷贝了父进程的全部内容 。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。4)若子进程先结束,父进程如果没有及时回收资源,子进程变成僵尸进程(要避免僵尸进程产生)
2.回收资源wait()
cpp
#include <sys/types.h>
#include <sys/wait.h>
/*
功能:回收进程资源(是一个阻塞函数)
参数:子进程的退出状态,不接受子进程状态的话就将参数设为NULL
返回值:成功 ---> 返回子进程的进程号
失败--->返回-1
*/
pid_t wait(int *wstatus);
cpp
#include <sys/types.h>
#include <sys/wait.h>
/*
功能:回收进程资源(是一个阻塞函数)
参数: pid:>0 指定某一个子进程
=-1 任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1 等待其组ID等于pid的绝对值的任一子进程
status:子进程退出状态
options:0 阻塞 WNOHANG 非阻塞
返回值: 成功:
当options的值设置成 0时:
返回结束的子进程的进程号
当options的值设置成WNOHANG时
若此时子进程已经结束,返回结束的子进程的进程号
若此时子进程已经结束,返回 0
失败:
返回-1
*/
pid_t waitpid(pid_t pid, int *wstatus, int options);
3.结束进程exit()
cpp
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
#include <stdlib.h>
void exit(int status);
功能:结束进程,刷新缓存
#include <unistd.h>
void _exit(int status);
功能:结束进程,不刷新缓存
4.获取进程号getpid()
cpp
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
在父进程中获取子进程的PID :通过fork()的返回值

三、进程间通信 (IPC)
1.通信方式介绍:
- 早期的进程间通信:无名管道(pipe) 有名管道(fifo)信号(signal)
- system V IPC: 共享内存 (share memory) 消息队列(message queue) 信号集(semaphore set)
- BSD: 套接字(socket)
2.无名管道
特点:
(1) 只能用于具有亲缘关系的进程之间的通信(2) 半双工 的通信模式,具有固定的读端fd[0]和写端fd[1]。
(3) 无名 管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
(4) 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
函数接口:
cpp
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端 fd[1]:写端
返回值:成功 0
失败 -1
3.有名管道
特点:
有名管道可以使互不相关的两个进程互相通信。
有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中,也就是内核空间中。
进程通过文件IO来操作有名管道
不支持如lseek() 操作
有名管道遵循先进先出规则
函数接口:
cpp
/*
功能:创健有名管道
参数:filename:有名管道文件名
mode:权限
返回值:成功:0
失败:-1,并设置errno号
*/
int mkfifo(const char *filename,mode_t mode);
4.信号
kill -l :查看系统中的信号
kill -num PID :给某个进程发信号
4.1概念:
- 信号是软件层面上对中断机制的一种模拟,是一种异步通信模式。
- 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
- 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
4.2信号的响应方式
- 忽略信号 :对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
- 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数
- 执行缺省操作:Linux对每种信号都规定了默认操作
4.3信号种类

4.4函数接口
(1)信号的发送kill()和挂起 pause()
cpp
#include <sys/types.h>
#include <signal.h>
/*
功能:信号发送
参数:pid:指定进程
sig:要发送的信号
返回值:成功 0
失败 -1
*/
int kill(pid_t pid, int sig);
cpp
#include <signal.h>
/*
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0
失败 -1
*/
int raise(int sig);
注意: raise( sig ) <==> kill ( getpid() , sig )
cpp
#include <unistd.h>
/*
功能:用于将调用进程挂起(类似于死循环且不占用CPU资源),直到收到被捕获处理的信号为止。
*/
int pause(void);
(2)定时器alarm()
cpp
#include <unistd.h>
/*
功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
*/
unsigned int alarm(unsigned int seconds);
注意:
1)一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
2)系统默认对SIGALRM(闹钟到点后内核发送的信号)信号的响应: 如果不对SIGALRM信号进行捕捉或采取措施,默认情况下,闹钟响铃时刻会退出进程(程序非正常结束,要注意刷新问题!! )。3)常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。
(3)信号处理函数signal()
cpp
#include <signal.h>
/*
功能:信号处理函数
参数:signum:要处理的信号
handler:信号处理方式
忽略信号 (SIG_IGN)
执行默认操作 (SIG_DFL)
捕捉信号并执行自定义操作 (自定义函数名)
返回值:成功:设置之前的信号处理方式
失败:-1
*/
sighandler_t signal(int signum, sighandler_t handler);
补充:
sighandler_t 是一个 函数指针 的重命名 (该指针指向的函数是一个 无返回值,形参为一个整型的类型函数)
typedef void (*sighandler_t)(int);
5. 共享内存
5.1特点
1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程
将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
- 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

5.2步骤
(1) 创建key值
(2) 创建或打开共享内存
(3) 映射共享内存到用户空间
(4) 撤销映射
(5) 删除共享内存
5.3函数接口
(1)创建key值
使用pathname提供的文件的inode号和字符的ASCII码值通过某种计算方法得到key值
cpp
#include <sys/types.h>
#include <sys/ipc.h>
/*
功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存
参数:
Pathname:已经存在的可访问文件的名字
Proj_id:一个字符(因为只用低8位,一个字符正好是8位)
返回值:成功:key值
失败:-1
*/
key_t ftok(const char *pathname, int proj_id);
(2) 创建或打开共享内存
cpp
#include <sys/ipc.h>
#include <sys/shm.h>
/*
功能:创建或打开共享内存
参数:
key 键值
size 共享内存的大小
shmflg IPC_CREAT|IPC_EXCL|0777
返回值:成功 返回共享内存号 shmid
出错或共享内存已存在 -1
*/
int shmget(key_t key, size_t size, int shmflg);
注意:shmget() 出错或共享内存已存在都返回 -1,因此,要在容错判断中在加一次判断,使用系统定义的file exist错误号进行判断
