课程网站:CourseGrading http://judge.buaa.edu.cn 第二讲 多进程程序设计 课程网站:CourseGrading http://judge.buaa.edu.cn 主讲教师: 赵长海 办公室: 新主楼G910 Email: zch@buaa.edu.cn Spring.

Slides:



Advertisements
Similar presentations
Linux 操作系统分析 中国科学技术大学计算机系 陈香兰( 0512 - ) Autumn 2010.
Advertisements

高级服务器设计和实现 1 —— 基础与进阶 余锋
阻塞操作. 在 linux 里,一个等待队列由一个 wait_queue_head_t 类型的结构来描述 等待队列的初始化: static wait_queue_head_t testqueue; init_waitqueue_head(&testqueue);
Linux 系统. 操作系统发展需求 1 没有操作系统 2 简单批处理操作系统 3 多道程序设计的批处理 4 多道程序设计的分时操作系统 5 多处理机并行系统 6 网络操作系统 7 分布式操作系统.
进 程. “ 程序 ” 和 “ 进程 ” 进程是 OS 对 CPU 执行的程序的运行过程的一种抽象。进程有自 己的生命周期,它由于任务的启动而创建,随着任务的完成(或 终止)而消亡,它所占用的资源也随着进程的终止而释放。 Linux 内核中通常把进程称为任务,每个进程主要通过一个称为进程描 述符(
河內塔(Hanoi)問題.
C语言程序设计 主讲教师 :张群燕 电话:
第1单元 操作系统概论 第一节 绪论 操作系统定义.
行程(process).
第一章 C语言概述 计算机公共教学部.
马志强 软件学院501室 网络应用开发 马志强 软件学院501室
Oracle数据库 Oracle 子程序.
第 5 章 文件I/O操作.
在PHP和MYSQL中实现完美的中文显示
Operating System Concepts 作業系統原理 Chapter 3 行程觀念 (Process Concept)
Kvm异步缺页中断 浙江大学计算机体系结构实验室 徐浩.
南京天石软件技术有限公司 陈锺 (QQ: Solaris 10 C编程 南京天石软件技术有限公司 陈锺 (QQ:
複習 struct score_Type{ int chinese,english; }; struct my_Type{
C File System.
chapter 1-Introduction
计算概论 第二十一讲 文件操作 北京大学信息学院.
Linux Programming – Process & Signal
第7章 Linux环境编程.
第四讲 MPI并行程序设计 课程网站:CourseGrading buaa.edu.cn 主讲教师: 赵长海
Chapter 3 行程觀念 (Process Concept)
多进程编程.
实践演练 广州创龙电子科技有限公司 01 广州创龙电子科技有限公司
临界区软件互斥软件实现算法.
进程操作.
Process management(程序管理)
第3章 堆栈和队列 堆栈 堆栈应用 队列 队列应用 优先级队列 主要知识点.
Windows网络操作系统管理 ——Windows Server 2008 R2.
文件读写实践 广州创龙电子科技有限公司 01 广州创龙电子科技有限公司
Linux操作系统分析 中国科学技术大学计算机系 陈香兰(0512- )
作業系統實習課(四) -檔案管理- 實驗室:720A 助教:鄧執中.
THE C PROGRAMMING LANGUAGE
本节内容 模拟线程切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
如何生成设备节点 广州创龙电子科技有限公司
临界区软件互斥软件实现算法 主讲教师:夏莹杰
Linux 文件操作——系统调用和标准 IO 库
实验一、进程控制 一、实验目的 1、加深对进程的理解,进一步认识并发执行的实质; 2、分析进程争用资源现象,学习解决进程互斥的方法;
第2章 进程和线程 内容提要: 2.1 进 程 概 念 2.2 进程的状态和组成 2.3 进 程 管 理 2.4 线 程.
Unit 11.Operating System 11.1 What’s OS 11.2 Related Courses
内容摘要 ■ 课程概述 ■ 教学安排 ■ 什么是操作系统? ■ 为什么学习操作系统? ■ 如何学习操作系统? ■ 操作系统实例
C语言程序设计 主讲教师:陆幼利.
本节内容 随机读取 视频提供:昆山爱达人信息技术有限公司.
DQMClientDim.cxx及双光子练习
作業系統 第四章 行程.
作業系統 第三章 作業系統結構.
进程概念.
3.5 线程 问题的提出 进程的引入使操作系统得以完成对并发执行的多道程序动态特征的描述和资源共享的管理,因而进程既是调度的基本单位又是资源分配的基本单位。进程所具有的这两个特点构成了程序并发执行的基础,但同时又导致进程切换过程中由于进程映像过大而带来的时空开销。因此,如果系统中创建的进程过多,或进程切换的频率过高,则会使系统效率下降,限制了并发度的进一步提高。
Select模型 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang 官网地址:
本节内容 Win32 API中的宽字符 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
信号量(Semaphore).
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
1.3操作系统提供的服务和用户接口 操作系统提供的用户接口 程序接口与系统调用 操作接口与系统程序
临界区问题的硬件指令解决方案 (Synchronization Hardware)
本节内容 Windows线程切换_时钟中断切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
WSAAsyncSelect 模型 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang
Computer Science & Information Management
实验二:添加Linux系统调用及熟悉常见系统调用
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab4.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 进程 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第四章 UNIX文件系统.
本节内容 SEMAPHORE 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
C语言程序设计 第13章 文件操作.
2019 Operating Systems 作業系統實習 助教:林欣穎 實驗室:720A Lab7.
《操作系统设计与实现》 Linux系统编程.
Presentation transcript:

课程网站:CourseGrading http://judge.buaa.edu.cn 第二讲 多进程程序设计 课程网站:CourseGrading http://judge.buaa.edu.cn 主讲教师: 赵长海 办公室: 新主楼G910 Email: zch@buaa.edu.cn Spring 2013

本章内容 2.1 进程的基本概念和特点 2.2 进程的控制 2.3 信号(signal) 2.4 进程间通信(进程协作的桥梁)

2.1 进程的概念和特点 一、进程相关的概念 进程:执行中的程序。系统中的每一个进程都运行在上下文(context)中。 2.1 进程的概念和特点 一、进程相关的概念 进程:执行中的程序。系统中的每一个进程都运行在上下文(context)中。 上下文:由程序正确运行所需要的状态组成,包括: 存放在内存中的代码和数据 堆栈、寄存器内容、程序计数器、环境变量、文件描述符 演示

上下文切换:就是从一个可执行进程切换到另一个可执行进程。引发上下文切换的原因: I/O request: Process is placed on I/O queue for a device Spawn and wait: Sometimes when a process creates a new process, it must wait until the child completes Time slice expiration Sleep request

系统调用:由操作系统提供的应用程序编程接口(API) 。 用户空间 内核

进程地址空间:虚拟地址0~2n-1,每个进程都有自己私有的地址空间,一个进程不可以直接访问另外一个进程的地址空间。

二、进程与线程区别 (1) 一个进程可以包括多个线程; (1) 一个进程可以包括多个线程; (2) 线程间共享地址空间,因此,一个线程修改一个变量(全局或静态),其它线程都能获知; (3) 线程间通信直接通过共享变量,而进程间通信则需要通过管道、消息队列等; 进程可以看做单线程进程 独享地址空间 共享地址空间

Shared memory segments, pipes, open files or mmap’d files 三、进程间通信模型 Shared memory segments, pipes, open files or mmap’d files DATA STACK TEXT processes Shared Memory maintained by kernel

每一个进程都有唯一的非零正数ID(PID) 2.2 进程控制 一、进程ID 每一个进程都有唯一的非零正数ID(PID) 获取进程ID #include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void); 调用者进程ID 调用者父进程进程ID

exit函数 二、进程终止 进程终止的三种方式: #include <stdlib.h> (1)收到一个信号,信号的默认行为是终止运行; (2)从主程序返回; (3)调用exit函数; exit函数 #include <stdlib.h> void exit(int status); 调用exit函数,主动退出运行

“fork”:把当前进程复制出一个新的进程,当前的进程就是新进程的父进程,新进程称为子进程。 三、进程创建 创建子进程 #include <sys/types.h> #include <unistd.h> pid_t fork(void); “fork”:把当前进程复制出一个新的进程,当前的进程就是新进程的父进程,新进程称为子进程。

例 示 如果没有exit 子进程 父进程 int glob = 6; int main() { pid_t pid; int x=1; pid = fork(); if(pid == 0) { glob++; printf(“child : glob=%d, x=%d\n”, glob, ++x); exit(0); } printf(“parent: glob=%d, x=%d\n”, glob, --x); 示 例 如果没有exit 演示 子进程 父进程

例2 示 子进程 父进程 int glob = 6; int main() { pid_t pid; int x=1; pid = fork(); if (pid < 0) { printf(“%s\n”, strerror(errno)); } else if (pid == 0) { glob++; printf(“child : glob=%d, x=%d\n”, glob, ++x); } else { printf(“parent: glob=%d, x=%d\n”, glob, --x); } 示 例2 子进程 父进程

fork 调用一次,返回两次 并发执行 (1) 返回到父进程; (2) 返回到新创建的子进程; (1) 宏观上,父子进程并发执行; (2) 微观上(多核系统),可能是交替执行或者并行执行;

fork 相同但是独立的地址空间 文件共享 (1) 复制:相同的用户栈、相同的本地变量、相同的堆、相同的全局变量……; (2) 独立:父子进程都有独立的私有地址空间,对变量的任何更改不会影响另外一个进程对应的变量; 文件共享 父进程打开的文件描述符,子进程可以继续使用;

次 1 多次调用 fork ? 进程图 { fork(); printf(“hello\n”); exit(0); } int main() 演示

次 2 进程图 { fork(); printf(“hello\n”); exit(0); } int main() 演示 fork

次 3 { fork(); printf(“hello\n”); exit(0); } int main() 演示 进程图 fork

次 3 if(pid1>0) { pid2=fork(); if(pid2>0) { fork(); } 进程图 { int main() { pid_t pid1, pid2; pid1 = fork(); if(pid1>0) { pid2=fork(); if(pid2>0) { fork(); } printf(“hello\n”); exit(0); 3 次 fork hello 进程图 演示

int main() { while(1) fork(); printf(“hello\n”); exit(0); } fork bomb

进程扇 父 子 pid_t createChild() { pid_t pid = fork(); if( pid == 0) { printf("child do something here!\n"); exit(0); } return pid; } int main() { pid_t childPid; int i, procNum = 3; for ( i = 0; i < procNum ; i++) { childPid = createChild(); } exit(0); } 进程扇 子 父

四、子进程回收 父进程长时间执行时,必须回收子进程 僵尸进程 父进程先退出 子进程先于父进程终止运行,但是尚未被父进程回收,内核并未清除该进程,保持在一种已终止状态,该进程称为“僵尸进程(僵死进程)” 父进程正在运行 演示:zombie.c 父进程长时间执行时,必须回收子进程 父进程先退出 演示:parent_exit.c

退 出 状 态 wait:父进程阻塞,直到一个子进程运行结束。 回收子进程 WIFEXITED (status) #include <sys/types.h> #include <sys/wait.h> pid_t wait(int * status); pid_t waitpid(pid_t pid, int *status, int options); wait:父进程阻塞,直到一个子进程运行结束。 演示:example_wait 退 出 状 态 WIFEXITED (status) WEXITSTATUS (status) WIFSIGNALED (status) WTERMSIG (status) WCOREDUMP(status) WIFSTOPPED(status) WSTOPSIG(status) WIFCONTINUED(status): since linux 2.6.10

void wait_exit(int status) { if (WIFEXITED(status)) 退出状态解析 void wait_exit(int status) { if (WIFEXITED(status)) printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? " (core file generated)" : ""); #else ""); #endif else if (WIFSTOPPED(status)) printf(“child stopped, signal number = %d\n”, WSTOPSIG(status)); } 正常退出 非正常退出 暂停(挂起) 演示:wait_exit.c

waitpid:比wait更加灵活,可以是非阻塞的。 回收子进程 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int * status); pid_t waitpid(pid_t pid, int *status, int options); waitpid:比wait更加灵活,可以是非阻塞的。

wait(&stat)等价于waitpid(-1, &stat, 0) 参数:pid_t pid pid > 0: 等待进程id为pid的子进程。 pid == 0: 等待与自己同组的任意子进程。 pid == -1: 等待任意一个子进程 pid < -1: 等待进程组号为-pid的任意子进程。 参数:int options 0:阻塞,即挂起调用进程; WNOHANG:非阻塞,即如果指定的子进程都还没有 终止运行,立即返回0;否则返回,终止运行进程ID; WUNTRACED:如果子进程挂起,则立即返回;默认只返回终止运行的进程; WUNTRACED | WNOHANG: wait(&stat)等价于waitpid(-1, &stat, 0)

int execve(const char *path, const char *argv[], 五、执行一个新程序 执行新程序 #include <unistd.h> int execve(const char *path, const char *argv[], const char *envp[]); 启动一个新的程序,新的地址空间完全覆盖当前进程的地址空间

环境变量:在操作系统中用来指定操作系统运行环境的一些参数 path:执行文件 argv:参数表 envp:环境变量表,一般直接用environ就行 环境变量:在操作系统中用来指定操作系统运行环境的一些参数 例 演示:example_execve.c char *argv[] = {“gcc”, “-g”, “-c”, “fork.c”, NULL}; execve(“/usr/bin/gcc”, argv, environ);

如何设计程序自动评判系统

1. 源代码上传 2. 编译源代码 基本方法 Web编程: 通过HTTP协议上传源代码文件至服务器 1)守护进程fork出子进程; 2)子进程内调execve,执行gcc char *argv[] = {“gcc”, “-o”, “3906”, “3906.c”, “-O2” NULL}; execve(“gcc”, argv, environ);

3. 执行 1)守护进程fork出子进程; 2)子进程内调execve,执行3806 3)调用waitpid: a)等待子进程结束 i)父进程计时,如果子进程在2秒内不结束 ,父进程 kill -9 pid b)获取结束状态 i)正常终止 ii)非正常终止: SIGSEGV、 SIGFPE

3. 执行伪代码 pid_t pid = fork(); if(pid == 0) { execv(“./3906”, argv, environ); exit(0); } 开始计时 int status; while(waitpid(pid, &status, WNOHANG){ if(超过规定时间) kill(pid); status,检查退出状态,错误原因

老师您好, 请问这段程序有什么问题. (作业的编程题的最后一题) #include <stdio. h> main() { 老师您好, 请问这段程序有什么问题?(作业的编程题的最后一题) #include <stdio.h> main() { int n,s=0,i=1; scanf("%d",&n); while(i<n+1) { s=s+i*(i+1)/2; i=i++; } printf("%d\n",s); return 0; } 为什么系统判定我运行时间过长? 谢谢您. addl $1, -8(%ebp) movl -12(%rbp), %edx leaq -12(%rbp), %rax incl (%rax) movl %edx, -12(%rbp)

2.3 信号(signals) 信号:也称作软中断,是Linux操作系统用来通知进程发生了某种异步事件的一种手段。 例

常见信号 SIGINT SIGKILL SIGTERM SIGALRM SIGCHLD SIGSEGV 前台程序执行过程中按下Ctrl-c就会向它发出SIGINT信号,默认终止进程 立即中止进程,不能被捕获或忽略 kill命令默认的中止程序信号 定时器到期,可用alarm函数来设置定时器。默认动作终止进程 子进程终止或停止,默认动作为忽略 演示:sigsegv.c 无效的内存引用

异常(系统调用、中断(时间片到期)、故障、终止) 信号检测与处理流程 用户进程 信号处理 用户进程 用户空间 内核空间 异常(系统调用、中断(时间片到期)、故障、终止) 异常处理 (系统调用、中断处理等) 检测到信号 返回到内核

一、发送信号 信号发送起因 内核通过更新目的进程上下文的某个状态(pending位向量,blocked位向量),来实现信号的发送。 内核检测到一个系统事件:除零、子进程终止、非法内存引用 一个进程调用了kill函数:显式要求内核向目的进程发送信号,目的进程可以是调用进程自身

kill函数 alarm函数 #include <sys/types.h> #include <signal.h> 信号发送函数 向其它进程(包括自己)发送信号 #include <sys/types.h> #include <signal.h> int kill(pid t pid, int sig); kill函数 给自己发送时钟信号(SIGALRM) #include <unistd.h> unsigned int alarm(unsigned int secs); alarm函数

例1 子进程sleep,只有信号才能将其唤醒 int main() { pid_t pid; /* child sleeps until SIGKILL signal received, then dies */ if ((pid = fork()) == 0) { pause(); /* wait for a signal to arrive */ printf("control should never reach here!\n"); exit(0); } /* parent sends a SIGKILL signal to a child */ kill(pid, SIGKILL); 父进程向子进程发信号 演示:example_kill.c

例2 给自己发信号 int main() { alarm(5); /* next SIGALRM will be delivered in 5s */ while (1) { ; /* signal handler returns control here each time */ } exit(0); 演示:example_alarm

二、接收信号 信号处理 当进程接收到一个信号(可能是自己发出,也可能是别的有权限的进程发出),它可以采取的动作可以是下面任意一种: 忽略信号:SIGSTOP与SIGKILL除外 捕获信号:通过系统调用,自定义接收到信号后采取的行动。SIGSTOP与SIGKILL除外 执行信号的默认动作

回调函数(callback function) #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 信号捕捉 函数指针 回调函数(callback function) typedef void (sighandler_t)(int); sighandler_t* signal(int signum, sighandler_t* handler); #include <signal.h> int sigaction(int signum, struct sigaction *act, struct sigaction *oldact); 信号捕捉

演示:example_alarm_handler 例 安装信号处理函数 void handler(int sig) { static int beeps = 0; printf("BEEP\n"); if (++beeps < 5) /* 1秒后再次发送信号*/ alarm(1); else { printf("BOOM!\n"); exit(0); } } int main() { signal(SIGALRM, handler); alarm(5); /* next SIGALRM will be delivered in 5s */ while (1) { ; /* signal handler returns control here each time */ } exit(0); 演示:example_alarm_handler

演示:example_kill_handler 例 void handler(int sig) { /*只有收到SIGTERM信号才终止运行*/ if(sig == SIGTERM) { printf("Catch signal SIGTERM\n"); exit(0); } else { printf("Sorry! you are signal %d, i will not exit!\n", sig); } } int main() { pid_t pid; /* 子进程内安装信号处理函数*/ if ((pid = fork()) == 0) { signal(SIGTERM, handler); signal(SIGINT, handler); while(1) pause(); } /*父进程向子进程发送信号 */ sleep(2); kill(pid, SIGINT); sleep(2); kill(pid, SIGTERM); } 演示:example_kill_handler

信号阻塞函数 #include <signal.h> 可以阻塞或者取消阻塞 #include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); 信号阻塞函数 信号阻塞:目的进程接收但是不处理该信号,直到取消阻塞。实现:blocked位向量存放被阻塞信号

例 执行结果 子进程 父进程 演示:example_badsig int sign = 0; int main() { pid_t pid; if ((pid = fork()) == 0){ signal (SIGUSR1, receive); //注册SIGUSR1的响应器 while ( sign==0 ) ;//原地踏步程序; sign = 0; //还原sign值 kill (getppid (), SIGUSR1); //给父进程发信号 exit(0); } //here, do something kill (pid, SIGUSR1); while (sign==0) ; //原地踏步程序,等待子进程的接受确认消息 sign = 0; //还原sign值 } void receive (int signo) { printf (“pid=%d,received\n”, getpid()); sign = 1; } 例 子进程 父进程 执行结果 演示:example_badsig

子进程在signal之前,收到了父进程发来的SIGUSR1信号 SIGUSR1的默认行为时终止运行 int sign = 0; int main() { pid_t pid; if ((pid = fork()) == 0){ signal (SIGUSR1, receive); //注册SIGUSR1的响应器 while ( sign==0 ) ;//原地踏步程序; sign = 0; //还原sign值 kill (getppid (), SIGUSR1); //给父进程发信号 exit(0); } //here, do something kill (pid, SIGUSR1); while (sign==0) ; //原地踏步程序,等待子进程的接受确认消息 sign = 0; //还原sign值 } 执行结果:主进程永远“忙等” 子进程 子进程在signal之前,收到了父进程发来的SIGUSR1信号 SIGUSR1的默认行为时终止运行 父进程

正确的程序 子进程 父进程 演示:example_sigmask int sign = 0; int main() { pid_t pid; if ((pid = fork()) == 0){ signal (SIGUSR1, receive); //注册SIGUSR1的响应器 while ( sign==0 ) ;//原地踏步程序; sign = 0; //还原sign值 kill (getppid (), SIGUSR1); //给父进程发信号 exit(0); } //here, do something kill (pid, SIGUSR1); while (sign==0) ; //原地踏步程序,等待子进程的接受确认消息 sign = 0; //还原sign值 } 正确的程序 sigemptyset(&chldmask); sigaddset(&chldmask, SIGUSR1); sigprocmask(SIG_BLOCK, &chldmask, &savemask); 阻塞SIGUSR1信号,即,如果收到SIGUSR1信号,不做处理,放到block位向量内 子进程 sigprocmask(SIG_SETMASK, &savemask, NULL); 取消SIGUSR1信号阻塞,即,将block位向量的信号,放到pending向量,并调用相应的信号处理函数 父进程 sigprocmask(SIG_SETMASK, &savemask, NULL); 取消SIGUSR1信号阻塞,即,将block位向量的信号,放到pending向量,并调用相应的信号处理函数 演示:example_sigmask

2.4 进程间通信(interprocess communication) 进程间通信(IPC):多个进程间协作或沟通的桥梁。

进程间通信方式 消息传递 共享内存 同步 maintained by kernel processes STACK 管道 消息队列 socket 共享内存 信号量 记录锁 DATA STACK TEXT processes maintained by kernel 共享内存 同步

POSIX: Portable Operating System Interface [for Unix] IPC进化史 POSIX: Portable Operating System Interface [for Unix] 最初的 UNIX IPC System V IPC 基于Socket的IPC Linux IPC POSIX IPC AT&T BSD POSIX Portable Operating System Interface [for Unix] Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及 BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成 了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。Linux则把两者继承了下来 由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(POSIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵 循POSIX标准 最初的Unix IPC:信号(signal)、管道、FIFO; System V IPC:System V 消息队列、 System V信号量、 System V共享内存; POSIX IPC:POSIX消息队列、 POSIX信号量、POSIX共享内存 

主要介绍 1. POSIX message queue  2. POSIX Semphore 3. POSIX共享内存

一、消息队列 普通队列示意图 an an-1 a3 a4 a2 a1 … 进队 出队 队头元素 队尾元素 先进先出

优先级 先进先出 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 … 最高优先级入队最早 最低优先级 入队最晚 Process Process receive send an an-1 a3 a4 a2 a1 … 消息 最高优先级入队最早 最低优先级 入队最晚 优先级 先进先出

特殊情况1 队列满 发送进程 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 阻塞 失败返回(非阻塞) … 1 Process Process receive send an an-1 a3 a4 a2 a1 … 消息 1 阻塞 发送进程 队列满 2 失败返回(非阻塞)

特殊情况2 队列空 接收进程 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 阻塞 失败返回(非阻塞) … 1 Process Process receive send an an-1 a3 a4 a2 a1 … 消息 1 阻塞 接收进程 队列空 2 失败返回(非阻塞)

消息队列属性 优先级 变长消息 POSIX消息队列可能实现 head next next NULL mq_maxmsg mq_maxsize priority=30 data next priority=20 data NULL priority=10 data length=1 length=2 length=3 消息队列属性 优先级 变长消息

打开消息队列 mqd_t mq_open(const char *name, int oflag); 消息队列操作 #include <mqueue.h> #include <sys/stat.h> #include <fcntl.h> mqd_t mq_open(const char *name, int oflag); mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr); 打开消息队列 打开已经 存在的队列 创建新 的队列 参数:int oflag O_RDONLY O_WRONLY O_RDWR O_NONBLOCK O_CREAT O_EXCL |

参数: struct mq_attr * attr 参数:const char *name 以/开头的字符串。除了开头的/,字符串内不能包含/ 参数: struct mq_attr * attr struct mq_attr { long mq_flags; /* Flags: 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue */ }; 创建的时候只有这两个 成员起作用 attr可以是 NULL

参数:mod_t mod 文件访问权限 常用权限设置 普通文件: 0644 可执行文件: 0755 文件夹: 0751 mode 宏 值(8进制) 涵义 S_IRUSR 00400 用户读【文件owner具有读权限】 S_IWUSR 00200 用户写 S_IXUSR 00100 用户执行 S_IRGRP 00040 组读【与owner同组的用户具有读权限】 S_IWGRP 00020 组写 S_IXGRP 00010 组执行 S_IROTH 00004 其他读【其他用户具有读权限】 S_IWOTH 00002 其他写 S_IXOTH 00001 其他执行 常用权限设置 普通文件: 0644 可执行文件: 0755 文件夹: 0751

例 创建队列 struct mq_attr attr; attr.mq_maxmsg = 10; attr.mq_msgsize = 100; attr.mq_flags = 0; mqd_t mqdes = mq_open(“/zch”, O_RDWR|O_CREAT, 0664, &attr); if(mqdes < 0) { printf(“%s\n", strerror(errno)); exit(-1); }

例 打开队列 (用于接收消息) mqd_t mqdes = mq_open(“/zch”, O_RDONLY); if(mqdes < 0) { printf(“%s\n", strerror(errno)); exit(-1); } 打开队列 (用于接收消息)

队列属性 int mq_getattr(mqd_t mqdes, struct mq_attr *attr); #include <mqueue.h> int mq_getattr(mqd_t mqdes, struct mq_attr *attr); int mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr); 队列属性 获取 队列属性 设置 队列属性 struct mq_attr { long mq_flags; /* Flags: 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue */ };

#include <mqueue.h> int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio); 发送消息 msg_ptr: 发送的消息缓冲区(传入) msg_len: 消息实际长度(传入) msg_prio: 消息优先级(传入) 例如: int msg; mq_send(mqdes, &msg, sizeof(int), 0);

返回消息实际大小 #include <mqueue.h> ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio); 接收消息 msg_ptr: 接收到的消息缓冲区(传入) msg_len: 消息缓冲区大小(传入) msg_prio: 接收消息的优先级(传出) 例如: char buffer[128]; ssize_t len = mq_receive(mqdes, buffer, 128, NULL);

关闭队列 删除队列 int mq_close(mqd_t mqdes); int mq_unlink(const char *name); #include <mqueue.h> int mq_close(mqd_t mqdes); 关闭队列 #include <mqueue.h> int mq_unlink(const char *name); 删除队列

求深不求广 有深必有广 复杂问题简单化 利用你所掌握的最成熟的技术 不要追求一步到位 计算机学习 程序设计 理解运行机理、重视基础学习 简单的问题(项目)深入去做 有深必有广 复杂问题简单化 程序设计 分而治之:分层次、分步骤、分阶段、分任务 利用你所掌握的最成熟的技术 非最新、最热门的技术 不要追求一步到位 程序设计是一个不断反复、重构的过程

例 已知N, 计算: 如何并行计算

需要做的工作 1. 划分任务:任务之间依赖性最小。 每一个i的计算 都是独立的 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 P1 P2 P3 P4 静态任务分配 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 P1 P2 P3 P4 P2 P3 P4 P1 P3 P2 P4 P3 P4 动态任务分配(能者多劳)

需要做的工作 1 2 i 5 … 4 3 1. 划分任务:任务之间依赖性最小。 2. 选择技术(动态任务分配): 2. 选择技术(动态任务分配): 父进程利用消息队列分任务,队列满,阻塞; 子进程从消息队列取任务,队列空阻塞; 回收子进程-waitpid; 子进程1 receive send 1 2 i 5 父进程 … 4 3 receive 子进程2

例 父进程 设置消息队列属性 打开消息队列 创建子进程 (=CPU数) 分配任务 (N=100000) 发送-1代表任务结束 等待子进程结束

例 子进程 从父进程传入的队列描述符 子进程接收任务 接收到-1,退出循环 执行计算任务 演示:example_mq

当多个子进程并行执行,同时receive消息队列中的队首消息时,怎么保证每个消息只被一个进程读取,而不会被多个读取? 问题1 当多个子进程并行执行,同时receive消息队列中的队首消息时,怎么保证每个消息只被一个进程读取,而不会被多个读取? 1. 对首消息拷贝到msg_ptr所指的地址,而不是对头消息的指针赋给msg_ptr 2. 消息队列receive函数内部保证对头消息的互斥访问! ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);

问题2 memcpy系统调用从缓冲区msg_buf读取msgSize长度的消息到recvValue。memcpy是不是一个原子操作?如何保证缓冲区msg_buf中的内容不会被多个进程同时读取? 1. memcpy不是原子操作 2. 由于进程的地址空间是独立的,每一个进程都有一个msg_buf,并不是被多个进程共享的,所以msg_buf的内容不会被多个进程同时读取。

父进程如何获得子进程计算结果并汇总 用两个消息队列 一个队列向子进程发送任务 新增一个队列接收子进程的计算结果

改进版 父进程 先删除 两个队列 (边界处理) 创建任务发送队列 创建任务接收队列 创建子进程 发送任务

父进程 改进版 任务结束,告知子进程 接收子进程计算结果 等待子进程结束

改进版 子进程 从父进程传入的队列描述符 子进程接收任务 接收到-1,退出循环 执行计算任务 计算结果发给父进程

二、信号量(named semaphores)

演示:example_sem_nolock 例 序号生成器,序号要保证唯一 应用:作业调度、打印池 int main(int argc, char ** argv) { FILE* fp; long i, seqno = 1; fp = fopen("seqno", "r+"); for (i = 0; i < 20; i++) { rewind(fp); /*rewind before read*/ fscanf(fp, "%ld\n", &seqno); printf("pid = %d, seq# = %ld\n", getpid(), seqno); seqno++; rewind(fp); /*rewind before write*/ fprintf(fp, "%ld\n", seqno); } fclose(fp); 从文件内 读序号 临界区 将当前序号 写入文件 演示:example_sem_nolock

同时只有1个或几个进程进入临界区 进程1 进程2 进程n sem_wait critical regions sem_post 用信号量保护临界区 sem_wait critical regions sem_post 进程1 进程2 进程n 同时只有1个或几个进程进入临界区

打开信号量 sem_t *sem_open(const char *name, int oflag); 信号量操作 #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); 打开信号量 打开已经 存在的信号量 创建新 的信号量 参数:int oflag O_CREAT O_EXCL| O_CREAT 打开已存在的 创建如果不存在 创建如果不存在,若存在,返回错误

参数:int value 最多允许并发访问的进程数 == 1: 互斥访问 > 1: 有限的并发访问

关闭信号量 删除信号量 int sem_close(sem_t *sem); #include <mqueue.h> int sem_close(sem_t *sem); 关闭信号量 #include <mqueue.h> int sem_unlink(const char *name); 删除信号量

int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); #include <mqueue.h> int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); test and decrement sem_wait: (1) 若信号量的值等于0,阻塞,直到值大于0; (2) 若信号量的值大于0,值减1; sem_trywait: 不会阻塞,返回-1,errno == EAGAIN; #include <mqueue.h> int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem, int*valp); increment sem_post: (1) 信号量的值加1,唤醒等待进程; (2) 若信号量的值大于0,值减1;

int sem_getvalue(sem_t *sem, int*valp); 取得等待的进程数目 #include <mqueue.h> int sem_getvalue(sem_t *sem, int*valp); 取得等待的进程数目 参数:int *valp *valp == 0:没有进程正在等待; *valp < 0:abs(*valp)个进程正在等待;

例 打开信号量 进入临界区 演示 fflush(fp) 出临界区 int main(int argc, char ** argv) { FILE* fp; long i, seqno = 1; fp = fopen("seqno", "r+"); sem_t *sem = sem_open("/semlock", O_CREAT, 0644, 1); for (i = 0; i < 20; i++) { sem_wait(sem); rewind(fp); /*rewind before read*/ fscanf(fp, "%ld\n", &seqno); printf("pid = %d, seq# = %ld\n", getpid(), seqno); seqno++; rewind(fp); /*rewind before write*/ fprintf(fp, "%ld\n", seqno); sem_post(sem); } fclose(fp); } 打开信号量 进入临界区 演示 fflush(fp) 出临界区

本章内容小结 本章内容小结

多 进 程 编 进程的基本概念 进程控制 信号 进程间通信 上下文、上下文切换、与线程的区别 系统调用 创建子进程,fork的特点 运行于内核空间 进程控制 创建子进程,fork的特点 子进程回收 调用wait:等待子进程结束、获得子进程结束状态 信号 信号在操作系统中的实现 信号的发送、信号捕获与处理 进程间通信 POSIX消息队列 POSIX信号量