进程(process)概念
进程——执行的程序
- 具有一个程序计数器(用于表示下个执行命令)和一组相关资源
- 包括进程堆栈stack(临时数据)和数据段data section(全局变量),有的还可能包含堆heap(动态分配内存)
进程状态
————(中断)————
↓ ↑
new—(成功)—>ready——(进行调度)——>run——(退出)——>terminated
等待结束↑ ↓ I/O或事件的等待
————wait<————
进程控制块(PCB)
用于保存进程的相关信息,用于在进程切换时保存状态和重新加载
相关信息:进程状态,程序计数器,CPU寄存器,CPU调度信息,内存管理信息,记账信息,I/O状态信息
线程
进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源
即一个进程可以并行运行多个线程
进程调度
作业队列——就绪队列——设备队列等等
OS通过调整PCB所在的队列来控制进程有序执行
调度器
-
长期调度程序(作业调度程序) 磁盘(存储设备)的缓冲池——>内存
-
短期调度程序(CPU调度程序) 准备执行的进程队列——>CPU
上下文切换
切换CPU到另一个进程需要保存当前进程状态和恢复另一个进程的状态
进程运行
创建
pid:进程的唯一标识符
进程树:Linux系统从根进程init开始,逐步创建子进程,形成进程树
ps -el #查看进程列表
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 1 0 0 80 0 - 41860 - ? 00:00:02 systemd
1 S 0 2 0 0 80 0 - 0 - ? 00:00:00 kthreadd
1 I 0 3 2 0 60 -20 - 0 - ? 00:00:00 rcu_gp
1 I 0 4 2 0 60 -20 - 0 - ? 00:00:00 rcu_par_gp
1 I 0 6 2 0 60 -20 - 0 - ? 00:00:00 kworker/0:
1 I 0 9 2 0 60 -20 - 0 - ? 00:00:00 mm_percpu_
1 S 0 10 2 0 80 0 - 0 - ? 00:00:00 rcu_tasks_
1 S 0 11 2 0 80 0 - 0 - ? 00:00:00 rcu_tasks_
1 S 0 12 2 0 80 0 - 0 - ? 00:00:00 ksoftirqd/
1 I 0 13 2 0 80 0 - 0 - ? 00:00:04 rcu_sched
部分结果如上图,进程1,2的父进程为0(init),其余进程的父进程为进程2(kthreadd)
进程创建子进程时,有两种执行可能:
- 父子进程并发执行
- 父进程等待子进程结束
新进程地址空间有两种可能:
- 父进程的复制品(与父进程同样的程序和数据)
- 加载新程序
fork()可以用来创建新进程(复制原来进程地址空间)
返回值——子进程返回0,父进程返回子进程的pid(非0)
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int status;
int a;
pid = fork();
if(pid<0){ //创建进程失败
fprintf(stderr, "Fork Failed");
return 1;
}
else if(pid==0){ //进入子进程
execlp("/bin/ls","ls",NULL); //执行命令ls
//子进程代码结束,自动exit()
}
else { //父进程
/*wait函数介绍
函数原型:extern __pid_t wait (int *__stat_loc);
Wait for a child to die. When one does, put its status in *STAT_LOC
and return its process ID. For errors, return (pid_t) -1.
*/
a=wait(&status); //父进程在子进程结束后,从wait()调用处开始继续
printf("child complete,PID: %d\n",a);
}
return 0;
}
终止
- 主动终止:exit()
- 被动终止:父进程终止子进程
- 子进程使用了超过它所分配的资源
- 分配给子进程的任务,不再需要
- 父进程退出,而且操作系统不允许无父进程的子进程继续执行(级联终止)
当进程终止时,系统会释放其资源,不过,它位于进程表中的条目还是存在的,直到其父进程调用了wait()
当进程终止,但是父进程未调用wait(),则称其为僵尸进程(一般是个暂时状态)
但是如果父进程没有调用wait()就终止了,这种子进程就成为孤儿进程——Linux解决方法为将init进程作为孤儿进程的父进程,并定期调用wait(),从而释放孤儿进程标识符和进程表条目。
进程通信
进程协作有利于:信息共享、计算加速(多核处理,多个子任务并行执行加速主任务)、模块化、方便(单用户同时执行多个任务)
进程间通信(IPC)机制实现进程协作,有两种方式:
-
共享内存
只需要建立共享区时进行内核调用,但是容易产生冲突
(在多核系统种会有高速缓存一致性问题,效率较低)
-
消息传递
无需避免冲突(交换少量数据很有用)但需要多次的系统调用
共享内存系统
POSIX(可移植系统接口,UNIX上系统API总称)具有多种IPC机制,包括共享内存和消息传递
其共享内存方法如下:
shm_open() 创建共享内存对象
ftruncate() 配置对象的大小(以字节为单位)
mmap() 创建内存映射文件,以便包含共享内存对象,返回指向内存映射文件的指针
//server.c
//创建共享内存并写入
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
const int size = 4096;
const char *name ="OS";
const char *message0 = "hello";
const char *message1 = "world";
int shm_fd;
void *ptr;
// name:共享内存对象名
// O_CREAT|O_RDWR:创建共享内存|读写方式打开
// 0666:目录权限
shm_fd = shm_open(name,O_CREAT|O_RDWR,0666);
//共享内存大小设置位size字节
ftruncate(shm_fd,size);
/*
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用, 文件实际上不会被更新。
fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明是匿名映射。
offset:被映射对象内容的起点。
*/
ptr = mmap(0,size,PROT_WRITE,MAP_SHARED,shm_fd,0);
sprintf(ptr,"%s",message0);
ptr += strlen(message1); //调整指针位置继续写入内存
sprintf(ptr,"%s",message1);
return 0;
}
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
const int size = 4096;
const char *name ="OS";
int shm_fd;
void *ptr;
//打开方式为只读
shm_fd = shm_open(name,O_RDONLY,0666);
ptr = mmap(0,size,PROT_READ,MAP_SHARED,shm_fd,0);
printf("%s",(char *)ptr);
//移除共享内存对象
shm_unlink(name);
return 0;
}
消息传递系统
-
直接通信
-
对称性寻址
send(P,message):向进程P发送message
receive(Q,message):从进程Q接受message
-
非对称性寻址
send(P,message):向进程P发送message
receive(id,message):从任何进程接受message,id时通信进程名
-
-
间接通信
两个进程共享一个邮箱
send(A,message):向邮箱A发送消息
receive(A,message):从邮箱A接受消息
其中,邮箱可以为进程或者操作系统拥有(前者进程终止则邮箱消失)
消息传递方式:
- 阻塞发送:发送进行阻塞,直到消息被接收进程接收
- 非阻塞发送:发送进程发送完成后,就恢复操作
- 阻塞接受:接收进程阻塞,直到有消息可用
- 非阻塞接受:接收进程收到一个有效消息或者空消息
通信进程交换的消息总是驻留在临时队列中,队列有三种实现方法:
- 零容量:链路中不能有任何消息处于等待状态(这种情况下,发送者应阻塞,直到接收到消息)
- 有限容量:队列满时,发送者阻塞
- 无线容量