Download presentation
Presentation is loading. Please wait.
Published by楚锦 尤 Modified 7年之前
1
课程网站:CourseGrading http://judge.buaa.edu.cn
第二讲 多进程程序设计 课程网站:CourseGrading 主讲教师: 赵长海 办公室: 新主楼G910 Spring 2013
2
本章内容 2.1 进程的基本概念和特点 2.2 进程的控制 2.3 信号(signal) 2.4 进程间通信(进程协作的桥梁)
3
2.1 进程的概念和特点 一、进程相关的概念 进程:执行中的程序。系统中的每一个进程都运行在上下文(context)中。
2.1 进程的概念和特点 一、进程相关的概念 进程:执行中的程序。系统中的每一个进程都运行在上下文(context)中。 上下文:由程序正确运行所需要的状态组成,包括: 存放在内存中的代码和数据 堆栈、寄存器内容、程序计数器、环境变量、文件描述符 演示
4
上下文切换:就是从一个可执行进程切换到另一个可执行进程。引发上下文切换的原因:
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
5
系统调用:由操作系统提供的应用程序编程接口(API) 。
用户空间 内核
6
进程地址空间:虚拟地址0~2n-1,每个进程都有自己私有的地址空间,一个进程不可以直接访问另外一个进程的地址空间。
7
二、进程与线程区别 (1) 一个进程可以包括多个线程;
(1) 一个进程可以包括多个线程; (2) 线程间共享地址空间,因此,一个线程修改一个变量(全局或静态),其它线程都能获知; (3) 线程间通信直接通过共享变量,而进程间通信则需要通过管道、消息队列等; 进程可以看做单线程进程 独享地址空间 共享地址空间
8
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
9
每一个进程都有唯一的非零正数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
10
exit函数 二、进程终止 进程终止的三种方式: #include <stdlib.h>
(1)收到一个信号,信号的默认行为是终止运行; (2)从主程序返回; (3)调用exit函数; exit函数 #include <stdlib.h> void exit(int status); 调用exit函数,主动退出运行
11
“fork”:把当前进程复制出一个新的进程,当前的进程就是新进程的父进程,新进程称为子进程。
三、进程创建 创建子进程 #include <sys/types.h> #include <unistd.h> pid_t fork(void); “fork”:把当前进程复制出一个新的进程,当前的进程就是新进程的父进程,新进程称为子进程。
12
例 示 如果没有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 演示 子进程 父进程
13
例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 子进程 父进程
14
fork 调用一次,返回两次 并发执行 (1) 返回到父进程; (2) 返回到新创建的子进程; (1) 宏观上,父子进程并发执行;
(2) 微观上(多核系统),可能是交替执行或者并行执行;
15
fork 相同但是独立的地址空间 文件共享 (1) 复制:相同的用户栈、相同的本地变量、相同的堆、相同的全局变量……;
(2) 独立:父子进程都有独立的私有地址空间,对变量的任何更改不会影响另外一个进程对应的变量; 文件共享 父进程打开的文件描述符,子进程可以继续使用;
16
次 1 多次调用 fork ? 进程图 { fork(); printf(“hello\n”); exit(0); } int main()
演示
17
次 2 进程图 { fork(); printf(“hello\n”); exit(0); } int main() 演示 fork
18
次 3 { fork(); printf(“hello\n”); exit(0); } int main() 演示 进程图 fork
19
次 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 进程图 演示
20
int main() { while(1) fork(); printf(“hello\n”); exit(0); } fork bomb
21
进程扇 父 子 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); } 进程扇 子 父
22
四、子进程回收 父进程长时间执行时,必须回收子进程 僵尸进程 父进程先退出
子进程先于父进程终止运行,但是尚未被父进程回收,内核并未清除该进程,保持在一种已终止状态,该进程称为“僵尸进程(僵死进程)” 父进程正在运行 演示:zombie.c 父进程长时间执行时,必须回收子进程 父进程先退出 演示:parent_exit.c
23
退 出 状 态 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
24
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
25
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更加灵活,可以是非阻塞的。
26
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)
27
int execve(const char *path, const char *argv[],
五、执行一个新程序 执行新程序 #include <unistd.h> int execve(const char *path, const char *argv[], const char *envp[]); 启动一个新的程序,新的地址空间完全覆盖当前进程的地址空间
28
环境变量:在操作系统中用来指定操作系统运行环境的一些参数
path:执行文件 argv:参数表 envp:环境变量表,一般直接用environ就行 环境变量:在操作系统中用来指定操作系统运行环境的一些参数 例 演示:example_execve.c char *argv[] = {“gcc”, “-g”, “-c”, “fork.c”, NULL}; execve(“/usr/bin/gcc”, argv, environ);
29
如何设计程序自动评判系统
30
1. 源代码上传 2. 编译源代码 基本方法 Web编程: 通过HTTP协议上传源代码文件至服务器 1)守护进程fork出子进程;
2)子进程内调execve,执行gcc char *argv[] = {“gcc”, “-o”, “3906”, “3906.c”, “-O2” NULL}; execve(“gcc”, argv, environ);
31
3. 执行 1)守护进程fork出子进程; 2)子进程内调execve,执行3806 3)调用waitpid: a)等待子进程结束
i)父进程计时,如果子进程在2秒内不结束 ,父进程 kill -9 pid b)获取结束状态 i)正常终止 ii)非正常终止: SIGSEGV、 SIGFPE
32
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,检查退出状态,错误原因
33
老师您好, 请问这段程序有什么问题. (作业的编程题的最后一题) #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)
34
2.3 信号(signals) 信号:也称作软中断,是Linux操作系统用来通知进程发生了某种异步事件的一种手段。 例
35
常见信号 SIGINT SIGKILL SIGTERM SIGALRM SIGCHLD SIGSEGV
前台程序执行过程中按下Ctrl-c就会向它发出SIGINT信号,默认终止进程 立即中止进程,不能被捕获或忽略 kill命令默认的中止程序信号 定时器到期,可用alarm函数来设置定时器。默认动作终止进程 子进程终止或停止,默认动作为忽略 演示:sigsegv.c 无效的内存引用
36
异常(系统调用、中断(时间片到期)、故障、终止)
信号检测与处理流程 用户进程 信号处理 用户进程 用户空间 内核空间 异常(系统调用、中断(时间片到期)、故障、终止) 异常处理 (系统调用、中断处理等) 检测到信号 返回到内核
37
一、发送信号 信号发送起因 内核通过更新目的进程上下文的某个状态(pending位向量,blocked位向量),来实现信号的发送。
内核检测到一个系统事件:除零、子进程终止、非法内存引用 一个进程调用了kill函数:显式要求内核向目的进程发送信号,目的进程可以是调用进程自身
38
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函数
39
例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
40
例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
41
二、接收信号 信号处理 当进程接收到一个信号(可能是自己发出,也可能是别的有权限的进程发出),它可以采取的动作可以是下面任意一种:
忽略信号:SIGSTOP与SIGKILL除外 捕获信号:通过系统调用,自定义接收到信号后采取的行动。SIGSTOP与SIGKILL除外 执行信号的默认动作
42
回调函数(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); 信号捕捉
43
演示: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
44
演示: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
45
信号阻塞函数 #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位向量存放被阻塞信号
46
例 执行结果 子进程 父进程 演示: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
47
子进程在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的默认行为时终止运行 父进程
48
正确的程序 子进程 父进程 演示: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
49
2.4 进程间通信(interprocess communication)
进程间通信(IPC):多个进程间协作或沟通的桥梁。
50
进程间通信方式 消息传递 共享内存 同步 maintained by kernel processes STACK
管道 消息队列 socket 共享内存 信号量 记录锁 DATA STACK TEXT processes maintained by kernel 共享内存 同步
51
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共享内存
52
主要介绍 1. POSIX message queue 2. POSIX Semphore 3. POSIX共享内存
53
一、消息队列 普通队列示意图 an an-1 a3 a4 a2 a1 … 进队 出队 队头元素 队尾元素 先进先出
54
优先级 先进先出 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 … 最高优先级入队最早 最低优先级 入队最晚
Process Process receive send an an-1 a3 a4 a2 a1 … 消息 最高优先级入队最早 最低优先级 入队最晚 优先级 先进先出
55
特殊情况1 队列满 发送进程 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 阻塞 失败返回(非阻塞) … 1
Process Process receive send an an-1 a3 a4 a2 a1 … 消息 1 阻塞 发送进程 队列满 2 失败返回(非阻塞)
56
特殊情况2 队列空 接收进程 an an-1 a3 a4 a2 a1 消息 消息队列(优先级队列)示意图 阻塞 失败返回(非阻塞) … 1
Process Process receive send an an-1 a3 a4 a2 a1 … 消息 1 阻塞 接收进程 队列空 2 失败返回(非阻塞)
57
消息队列属性 优先级 变长消息 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 消息队列属性 优先级 变长消息
58
打开消息队列 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 |
59
参数: 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
60
参数: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
61
例 创建队列 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, , &attr); if(mqdes < 0) { printf(“%s\n", strerror(errno)); exit(-1); }
62
例 打开队列 (用于接收消息) mqd_t mqdes = mq_open(“/zch”, O_RDONLY);
if(mqdes < 0) { printf(“%s\n", strerror(errno)); exit(-1); } 打开队列 (用于接收消息)
63
队列属性 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 */ };
64
#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);
65
返回消息实际大小 #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);
66
关闭队列 删除队列 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); 删除队列
67
求深不求广 有深必有广 复杂问题简单化 利用你所掌握的最成熟的技术 不要追求一步到位 计算机学习 程序设计 理解运行机理、重视基础学习
简单的问题(项目)深入去做 有深必有广 复杂问题简单化 程序设计 分而治之:分层次、分步骤、分阶段、分任务 利用你所掌握的最成熟的技术 非最新、最热门的技术 不要追求一步到位 程序设计是一个不断反复、重构的过程
68
例 已知N, 计算: 如何并行计算
69
需要做的工作 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 动态任务分配(能者多劳)
70
需要做的工作 1 2 i 5 … 4 3 1. 划分任务:任务之间依赖性最小。 2. 选择技术(动态任务分配):
2. 选择技术(动态任务分配): 父进程利用消息队列分任务,队列满,阻塞; 子进程从消息队列取任务,队列空阻塞; 回收子进程-waitpid; 子进程1 receive send 1 2 i 5 父进程 … 4 3 receive 子进程2
71
例 父进程 设置消息队列属性 打开消息队列 创建子进程 (=CPU数) 分配任务 (N=100000) 发送-1代表任务结束 等待子进程结束
72
例 子进程 从父进程传入的队列描述符 子进程接收任务 接收到-1,退出循环 执行计算任务 演示:example_mq
73
当多个子进程并行执行,同时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);
74
问题2 memcpy系统调用从缓冲区msg_buf读取msgSize长度的消息到recvValue。memcpy是不是一个原子操作?如何保证缓冲区msg_buf中的内容不会被多个进程同时读取? 1. memcpy不是原子操作 2. 由于进程的地址空间是独立的,每一个进程都有一个msg_buf,并不是被多个进程共享的,所以msg_buf的内容不会被多个进程同时读取。
75
父进程如何获得子进程计算结果并汇总 用两个消息队列 一个队列向子进程发送任务 新增一个队列接收子进程的计算结果
76
改进版 父进程 先删除 两个队列 (边界处理) 创建任务发送队列 创建任务接收队列 创建子进程 发送任务
77
父进程 改进版 任务结束,告知子进程 接收子进程计算结果 等待子进程结束
78
改进版 子进程 从父进程传入的队列描述符 子进程接收任务 接收到-1,退出循环 执行计算任务 计算结果发给父进程
79
二、信号量(named semaphores)
80
演示: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
81
同时只有1个或几个进程进入临界区 进程1 进程2 进程n sem_wait critical regions sem_post
用信号量保护临界区 sem_wait critical regions sem_post 进程1 进程2 进程n 同时只有1个或几个进程进入临界区
82
打开信号量 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 打开已存在的 创建如果不存在 创建如果不存在,若存在,返回错误
83
参数:int value 最多允许并发访问的进程数 == 1: 互斥访问 > 1: 有限的并发访问
84
关闭信号量 删除信号量 int sem_close(sem_t *sem);
#include <mqueue.h> int sem_close(sem_t *sem); 关闭信号量 #include <mqueue.h> int sem_unlink(const char *name); 删除信号量
85
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;
86
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)个进程正在等待;
87
例 打开信号量 进入临界区 演示 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) 出临界区
88
本章内容小结 本章内容小结
89
多 进 程 编 进程的基本概念 进程控制 信号 进程间通信 上下文、上下文切换、与线程的区别 系统调用 创建子进程,fork的特点
运行于内核空间 进程控制 创建子进程,fork的特点 子进程回收 调用wait:等待子进程结束、获得子进程结束状态 信号 信号在操作系统中的实现 信号的发送、信号捕获与处理 进程间通信 POSIX消息队列 POSIX信号量
Similar presentations