进程间通信

匿名管道

进程间通信的必要性

单进程的,那么也就无法使用并发能力,更加无法实现多进程协同

传输数据,同步执行流,消息通知等,不是目的,而是手段,企图实现多进程协同。

进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程运行具有独立性–-进程想通信,难度其实是比较大的--进程间通信的本质:先让不同的进程看到同一份资源(内存空间)

为什么要进行进程间通信-- 交互数据、控制、通知等目标

进程间通信的技术背景

1.进程是具有独立性的。虚拟地址空间+页表保证进程运行的独立性(进程内核数据结构+进程的代码和数据)

2.通信成本会比较高!

进程间通信的本质理解

1.进程间通信的前提,首先需要让不同的进程看到同一块"内存"(特定的结构组织的)

2.所以你所谓的进程看到同一块"内存",属于哪一个进程呢﹖不能隶属于任何一个进程,而应该更强调共享。

计算机通信领域的设计者,设计了一种单向通信的方式--—管道

注意注意:管道是单向通信的

原理

新的函数

PIPE(2) Linux Programmer's Manual PIPE(2) NAME pipe, pipe2 - create pipe SYNOPSIS #include <unistd.h> int pipe(int pipefd[2]); #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <fcntl.h> /* Obtain O_* constant definitions */ #include <unistd.h>

pipefd[2] :输出型参数,期望通过调用它,得到被打开的文件fd。

fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:成功返回0,失败返回错误代码

demo代码

如何做到让不同的进程,看到同一份资源的呢?-- fork让子进程继承的--能够让具有血缘关系的进程进行进程间通信―常用于父子进程

#include <iostream> #include <string> #include <cstdio> #include <cstring> #include <assert.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; // 为什么不定义全局buffer来进行通信呢?? 因为有写时拷贝的存在,无法更改通信! int main() { // 1. 创建管道 int pipefd[2] = {0}; // pipefd[0(嘴巴,读书)]: 读端 , pipefd[1(钢笔,写)]: 写端 int n = pipe(pipefd); assert(n != -1); // debug assert, release assert (void)n; #ifdef DEBUG cout << "pipefd[0]: " << pipefd[0] << endl; // 3 cout << "pipefd[1]: " << pipefd[1] << endl; // 4 #endif // 2. 创建子进程 pid_t id = fork(); assert(id != -1); if (id == 0) { //子进程 - 读 // 3. 构建单向通信的信道,父进程写入,子进程读取 // 3.1 关闭子进程不需要的fd close(pipefd[1]); char buffer[1024 * 8]; while (true) { // sleep(20); // 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等 // 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾! ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1); if (s > 0) { buffer[s] = 0; cout << "child get a message[" << getpid() << "] Father# " << buffer << endl; } else if(s == 0) { cout << "writer quit(father), me quit!!!" << endl; break; } } // close(pipefd[0]); exit(0); } //父进程 - 写 // 3. 构建单向通信的信道 // 3.1 关闭父进程不需要的fd close(pipefd[0]); string message = "我是父进程,我正在给你发消息"; int count = 0; char send_buffer[1024 * 8]; while (true) { // 3.2 构建一个变化的字符串 snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++); // 3.3 写入 write(pipefd[1], send_buffer, strlen(send_buffer)); // 3.4 故意sleep sleep(1); cout << count << endl; if (count == 5){ cout << "writer quit(father)" << endl; break; } } close(pipefd[1]); pid_t ret = waitpid(id, nullptr, 0); cout << "id : " << id << " ret: " << ret <<endl; assert(ret > 0); (void)ret; return 0; }

注意这里涉及到了snprintf。printf可以把数据打印到屏幕上,fprintf可以将数据打印到文件中。sprintf可以将数据打印到字符串中。

总结管道的特点,理解以前的管道|

1.管道是用来进行具有血缘关系的进程进性进程间通信-- 常用于父子通信

2.管道具有通过让进程间协同,提供了访问控制!—

3.管道提供的是面向流式的通信服务--面向字节流--_协议

4.管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的!

5.管道是单向通信的,就是半双工通信的一种特殊情况

半双工通信:正常人的交流

全双工通信:两个人在街上互相问候

注意是 要么 要么 ,不是 只能 。

四种访问控制

a.写快,读慢,写满不能在写了

b.写慢,读快,管道没有数据的时候,读必须等待

c.写关,读0,标识读到了文件结尾

d.读关,写继续写,OS终止写进程

管道是一个文件―读取–具有访问控制

显示器也是一个文件,父子同时往显示器写入的时候,有没有说一个会等另一个的情况呢?-缺乏访问控制

扩展

task.hpp

#pragma once #include <iostream> #include <string> #include <vector> #include <unordered_map> #include <unistd.h> #include <functional> typedef std::function<void()> func; std::vector<func> callbacks; std::unordered_map<int, std::string> desc; void readMySQL() { std::cout << "sub process[" << getpid() << " ] 执行访问数据库的任务\n" << std::endl; } void execuleUrl() { std::cout << "sub process[" << getpid() << " ] 执行url解析\n" << std::endl; } void cal() { std::cout << "sub process[" << getpid() << " ] 执行加密任务\n" << std::endl; } void save() { std::cout << "sub process[" << getpid() << " ] 执行数据持久化任务\n" << std::endl; } void load() { desc.insert({callbacks.size(), "readMySQL: 读取数据库"}); callbacks.push_back(readMySQL); desc.insert({callbacks.size(), "execuleUrl: 进行url解析"}); callbacks.push_back(execuleUrl); desc.insert({callbacks.size(), "cal: 进行加密计算"}); callbacks.push_back(cal); desc.insert({callbacks.size(), "save: 进行数据的文件保存"}); callbacks.push_back(save); } void showHandler() { for(const auto &iter : desc ) { std::cout << iter.first << "\t" << iter.second << std::endl; } } int handlerSize() { return callbacks.size(); }

进程池 ProcessPool.cc

#include <iostream> #include <vector> #include <cstdlib> #include <ctime> #include <cassert> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include "Task.hpp" #define PROCESS_NUM 5 using namespace std; int waitCommand(int waitFd, bool &quit) //如果对方不发,我们就阻塞 { uint32_t command = 0; ssize_t s = read(waitFd, &command, sizeof(command)); if (s == 0) { quit = true; return -1; } assert(s == sizeof(uint32_t)); return command; } void sendAndWakeup(pid_t who, int fd, uint32_t command) { write(fd, &command, sizeof(command)); cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl; } int main() { // 代码中关于fd的处理,有一个小问题,不影响我们使用,但是你能找到吗?? load(); // pid: pipefd vector<pair<pid_t, int>> slots; // 先创建多个进程 for (int i = 0; i < PROCESS_NUM; i++) { // 创建管道 int pipefd[2] = {0}; int n = pipe(pipefd); assert(n == 0); (void)n; pid_t id = fork(); assert(id != -1); // 子进程我们让他进行读取 if (id == 0) { // 关闭写端 close(pipefd[1]); // child while (true) { // pipefd[0] // 等命令 bool quit = false; int command = waitCommand(pipefd[0], quit); //如果对方不发,我们就阻塞 if (quit) break; // 执行对应的命令 if (command >= 0 && command < handlerSize()) { callbacks[command](); } else { cout << "非法command: " << command << endl; } } exit(1); } // father,进行写入,关闭读端 close(pipefd[0]); // pipefd[1] slots.push_back(pair<pid_t, int>(id, pipefd[1])); } // 父进程派发任务 srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机 while (true) { // 选择一个任务, 如果任务是从网络里面来的? int command = rand() % handlerSize(); // 选择一个进程 ,采用随机数的方式,选择进程来完成任务,随机数方式的负载均衡 int choice = rand() % slots.size(); // 把任务给指定的进程 sendAndWakeup(slots[choice].first, slots[choice].second, command); sleep(1); // int select; // int command; // cout << "############################################" << endl; // cout << "# 1. show funcitons 2.send command #" << endl; // cout << "############################################" << endl; // cout << "Please Select> "; // cin >> select; // if (select == 1) // showHandler(); // else if (select == 2) // { // cout << "Enter Your Command> "; // // 选择任务 // cin >> command; // // 选择进程 // int choice = rand() % slots.size(); // // 把任务给指定的进程 // sendAndWakeup(slots[choice].first, slots[choice].second, command); // } // else // { // } } // 关闭fd, 所有的子进程都会退出 for (const auto &slot : slots) { close(slot.second); } // 回收所有的子进程信息 for (const auto &slot : slots) { waitpid(slot.first, nullptr, 0); } }

makeflie

ProcessPool:ProcessPool.cc g++ -o $@ $^ -std=c++11 #-DDEBUG .PHONY:clean clean: rm -f ProcessPool

命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

命名管道是一种特殊类型的文件

管道文件!有名字

可以被打开,但是不会将内存数据进行刷新到磁盘

该文件一定在系统路径中,路径具有唯一性!

双方进程,就可以通过,管道文件的路径,看到同一份资源!!

mkfifo

MKFIFO(3) Linux Programmer's Manual MKFIFO(3) NAME mkfifo - make a FIFO special file (a named pipe) SYNOPSIS #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);

返回值

RETURN VALUE On success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno is set appropriately).

先来给小实验

MKFIFO(1) User Commands MKFIFO(1) NAME mkfifo - make FIFOs (named pipes)

[hang@VM-4-12-centos day14]$ mkfifo pipe [hang@VM-4-12-centos day14]$ ls -l total 4 -rw-rw-r-- 1 hang hang 0 Feb 13 20:10 1.cc prw-rw-r-- 1 hang hang 0 Feb 13 21:13 pipe -rw-rw-r-- 1 hang hang 66 Feb 13 17:33 test.cpp [hang@VM-4-12-centos day14]$ echo "hello" >pipe [hang@VM-4-12-centos day14]$

[hang@VM-4-12-centos day14]$ ls -l total 4 -rw-rw-r-- 1 hang hang 0 Feb 13 20:10 1.cc prw-rw-r-- 1 hang hang 0 Feb 13 21:13 pipe -rw-rw-r-- 1 hang hang 66 Feb 13 17:33 test.cpp [hang@VM-4-12-centos day14]$ cat < pipe hello [hang@VM-4-12-centos day14]$

现在是我们的正经代码

makefile

.PHONY:all all:client mutiServer client:client.cxx g++ -o $@ $^ -std=c++11 mutiServer:server.cxx g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f client mutiServer

Log.hpp

#ifndef _LOG_H_ #define _LOG_H_ #include <iostream> #include <ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; return std::cout; } #endif

comm.hpp

#ifndef _COMM_H_ #define _COMM_H_ #include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "Log.hpp" using namespace std; #define MODE 0666 #define SIZE 128 string ipcPath = "./fifo.ipc"; #endif

server.cxx

#include "comm.hpp" #include <sys/wait.h> static void getMessage(int fd) { char buffer[SIZE]; while (true) { memset(buffer, '\0', sizeof(buffer)); ssize_t s = read(fd, buffer, sizeof(buffer) - 1); if (s > 0) { cout <<"[" << getpid() << "] "<< "client say> " << buffer << endl; } else if (s == 0) { // end of file cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl; break; } else { // read error perror("read"); break; } } } int main() { // 1. 创建管道文件 if (mkfifo(ipcPath.c_str(), MODE) < 0) { perror("mkfifo"); exit(1); } Log("创建管道文件成功", Debug) << " step 1" << endl; // 2. 正常的文件操作 int fd = open(ipcPath.c_str(), O_RDONLY); if (fd < 0) { perror("open"); exit(2); } Log("打开管道文件成功", Debug) << " step 2" << endl; int nums = 3; for (int i = 0; i < nums; i++) { pid_t id = fork(); if (id == 0) { // 3. 编写正常的通信代码了 getMessage(fd); exit(1); } } for(int i = 0; i < nums; i++) { waitpid(-1, nullptr, 0); } // 4. 关闭文件 close(fd); Log("关闭管道文件成功", Debug) << " step 3" << endl; unlink(ipcPath.c_str()); // 通信完毕,就删除文件 Log("删除管道文件成功", Debug) << " step 4" << endl; return 0; }

client.cxx

#include "comm.hpp" int main() { // 1. 获取管道文件 int fd = open(ipcPath.c_str(), O_WRONLY); if(fd < 0) { perror("open"); exit(1); } // 2. ipc过程 string buffer; while(true) { cout << "Please Enter Message Line :> "; std::getline(std::cin, buffer); write(fd, buffer.c_str(), buffer.size()); } // 3. 关闭 close(fd); return 0; }

删除管道文件可以

[hang@VM-4-12-centos day14]$ rm -f [hang@VM-4-12-centos day14]$ unlink


管道读写规则

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

PIPE_BUF POSIX.1-2001 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence. Writes of more than PIPE_BUF bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are mul‐ tiple writers to the pipe, and on n, the number of bytes to be written:


system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到

内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

共享内存的理解

共享内存提供者,是操作系统

操作系统要不要管理共享内存?当然要―〉先描述在组织-〉重新理解共享内存=共享内存块+对应的共享内存的内核数据结构!

函数

shmget

NAME shmget - allocates a System V shared memory segment SYNOPSIS #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); RETURN VALUE On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.

返回值

共享内存的用户层标识符,类似曾经的fd

key

要通信的对方进程,怎么保证,对方能看到,并且看到的就是我创建的共享内存呢?? ?

通过key,数据是几,不重要只要能够在系统唯一即可-〉server && client -〉使用同一个key ->只要key值相同,就是看到了同一个共享内存!

锁匠的故事

那么如何生成一个唯一的key呢??下一个函数会解释。

size

共享内存的大小

shmflg

IPC_CREAT to create a new segment. If this flag is not used, then shmget() will find the segment associated with key and check to see if the user has permission to access the segment. IPC_EXCL used with IPC_CREAT to ensure failure if the segment already exists. mode_flags (least significant 9 bits) specifying the permissions granted to the owner, group, and world. These bits have the same format, and the same meaning, as the mode argument of open(2). Presently, the execute permis‐ sions are not used by the system.

IPC_CREAT

单独的话,如果创建共享内存,如果底层已经存在,获取之,并且返回,如果不存在,创建之,并返回。

IPC_EXCL

单独使用IPC_EXCL,没有意义

IPC_CREAT and IPC_EXCL

如果底层不存在,创建之,并返回如果底层存在,出错返回。

返回成功的话,定是一个全新的shm !

ftok

NAME ftok - convert a pathname and a project identifier to a System V IPC key SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);

第一个就是一个路径,第二个随便给其实都可以的

shmat

NAME shmat, shmdt - System V shared memory operations SYNOPSIS #include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);

将我们的进程挂接到内存空间上。

第二个就是我们的虚拟地址空间,但是呢,我们严重不推荐使用,建议设为nullptr,让操作系统自己去挂接。第三个是挂接方式

这个函数的返回值值得关注.

RETURN VALUE On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate the cause of the error. On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.

与我们的malloc相似。成功的时候返回共享空间的地址。

shmdt

NAME shmat, shmdt - System V shared memory operations SYNOPSIS #include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr);

去关联进程与共享内存地址

shmctl

关闭管道

NAME shmctl - System V shared memory control SYNOPSIS #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存 n = shmctl(shmid, IPC_RMID, nullptr);

代码实现

makefile

.PHONY:all all:shmClient shmServer shmClient:shmClient.cc g++ -o $@ $^ -std=c++11 shmServer:shmServer.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f shmClient shmServer

shmClient.cc

#include "comm.hpp" int main() { key_t k = ftok(PATH_NAME, PROJ_ID); if (k < 0) { Log("create key failed", Error) << " client key : " << k << endl; exit(1); } Log("create key done", Debug) << " client key : " << k << endl; // 获取共享内存 int shmid = shmget(k, SHM_SIZE, 0); if(shmid < 0) { Log("create shm failed", Error) << " client key : " << k << endl; exit(2); } Log("create shm success", Error) << " client key : " << k << endl; sleep(10); char *shmaddr = (char *)shmat(shmid, nullptr, 0); if(shmaddr == nullptr) { Log("attach shm failed", Error) << " client key : " << k << endl; exit(3); } Log("attach shm success", Error) << " client key : " << k << endl; sleep(10); //使用 // 去关联 int n = shmdt(shmaddr); assert(n != -1); Log("detach shm success", Error) << " client key : " << k << endl; sleep(10); // client 要不要chmctl删除呢?不需要!! return 0; }

shmServer.cc

#include "comm.hpp" string TransToHex(key_t k) { char buffer[32]; snprintf(buffer, sizeof buffer, "0x%x", k); return buffer; } int main() { // 1. 创建公共的Key值 key_t k = ftok(PATH_NAME, PROJ_ID); assert(k != -1); Log("create key done", Debug) << " server key : " << TransToHex(k) << endl; // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者 int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); // if (shmid == -1) { perror("shmget"); exit(1); } Log("create shm done", Debug) << " shmid : " << shmid << endl; sleep(10); // 3. 将指定的共享内存,挂接到自己的地址空间 char *shmaddr = (char *)shmat(shmid, nullptr, 0); Log("attach shm done", Debug) << " shmid : " << shmid << endl; sleep(10); // 这里就是通信的逻辑了 // 4. 将指定的共享内存,从自己的地址空间中去关联 int n = shmdt(shmaddr); assert(n != -1); (void)n; Log("detach shm done", Debug) << " shmid : " << shmid << endl; sleep(10); // 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存 n = shmctl(shmid, IPC_RMID, nullptr); assert(n != -1); (void)n; Log("delete shm done", Debug) << " shmid : " << shmid << endl; return 0; }

Log.hpp

#ifndef _LOG_H_ #define _LOG_H_ #include <iostream> #include <ctime> #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; return std::cout; } #endif

comm.hpp

#pragma once #include <iostream> #include <cstdio> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <cassert> #include "Log.hpp" using namespace std; //不推荐 #define PATH_NAME "/home/whb" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍

注意的是,这里面有key 和shmid。

key是计算机层面标识共性内存的,shmid是用户层面用来标识的

system V IPC资源,生命周期随内核!

当进程运行结束,我们的共享内存,还存在!!

1.手动删除2.代码删除

加入了一个访问控制

关于共享内存空间:

这是共享空间的示意图

共享内存位于堆和栈中间的共享区域,这是属于用户空间的,我们以前学习地址空间的时候,我们知道在[3,4]GB这是属于内核空间,下面的[0,3]GB这是属于用户空间的。

如果处于用户的内核空间,那么就可以不经过系统调用,直接可以访问!!这个时候,双方要进行通信,可以直接进行内存级的读和写即可!!这个内存空间加载的位置是不固定的!而由于最上面的一GB是系统空间的,它是固定的,如果要进行系统访问,就可以到固定位置直接进行访问。因为所有的上面的内核空间是一样的。

我们之前写的管道为什么要使用read和write这中系统级别的接口呢??

管道是一个文件,保证让两个进程可以看到同一份资源,文件是属于内核空间的(是一种内核数据结构),文件是由OS维护的,无权直接进行访问,我们进行访问的话,当然要需要调用系统级别的函数。

共享内存是最快的???这是啥原因呢???

从键盘到我们的C标准库提供的缓冲区,C标准库的缓冲区到系统接口write的缓冲区(其实已经到了操作系统的内部了,就差刷新到磁盘上了),再到我们的C标准库,再到我们的printf 。这其中就包含了四次拷贝。

而我们的共享内存,从键盘到共享内存。再到printf这里。其中也就只是涉及到了两次拷贝。


信号量

基与共享内存的理解

为了让进程间通信-->让不同的进程之间,看到同一份资源-->>我们之前讲的所有的通信方式,本质都是优先解决一个问题:让不同的

进程看到同一份资源!

让不用的进程看到了同一份资源,比如共享内存,也带来了一些时序问题,造成数据不一致问题!

我们把多个进程(执行流)看到的公共的一份资源--临界资源

我们把自己的进程,访问临界资源的代码--临界区

为了更好地进行临界区的保护,可以让多执行流在任何时刻,都只能有一个进程进入临界区--互斥!!---->>原子性:要么不做,要么做完,没有中间状态,就称之为原子性.

所以!多个执行流,互相运行的时候互相干扰,主要是我们不加保护的访问了同样的资源(临界资源),在非临界区多个执行流互相是不影响的!


看电影一定要有座位(放映厅里面的一个资源),这个座位真正属于你,是不是你自己坐在这个位置上,这个座位才属于你?不是!!

先买票,只要我买了票,我就拥有了这个座位。

买票的本质:对座位的│预定|机制

每一个进程想进入临界资源,访问临界资源中的一部分,不能让进程直接去使用临界资源(不能让用户直接去电影院内部占座位),你的先申请信号量(你的先买票!! )

所以信号量的本质(不太准确)是一个计数器,类似于int count = n;

申请信号量

1.申请信号量的本质:让信号量计数器--

2.主要申请信号量成功,临界资源内部,一定给你预留了你想要的资源--申请信号量本质其实是对临界资源的一种预定机制

申请信号量--

访问临界资源进程执行自己的临界区代码

释放信号量++

以CPU执行n--为例,分析CPU的工作步骤

计算要在CPU内,数据在内存的n变量里面

CPU执行指令的时候

1.将内存中的数据加载到cpu内的寄存器中(读指令)

2.n--(分析&&执行指令)

3.将CPU修改完毕的n写回内存(写回结果)

值得注意的是:

执行流在执行的时候,在任何时刻都可能被切换!!

寄存器只有一套,被所有的执行流共享,但是寄存器里面的数据,属于每一个执行流,属于该执行流的上下文数据!!------>>上下文保护&&上下文恢复的!

以我们的client和server为例,

由于我们的n--这个操作在CPU看来是有三步的,那么就可能存在一种情况们就是,我们client,运行到一半时,然后被切换,再调度server,可能会导致差错。

如果一个n--操作只有一行汇编,该操作是原子的!!

信号量计数器--------->>是对临界资源的预定机制!!

申请信号量---->>计数器-- --->>> P操作 ->必须是原子的

释放信号量----->>计数器++ ---->>> V操作->必须是原子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值