当前位置: 首页 > news >正文

单机网页游戏网站招商外包

单机网页游戏网站,招商外包,东莞市网络策划推广哪家好,wordpress模板工作室文章目录 一、什么是进程间通信二、管道1.匿名管道(pipe)a).创建匿名管道b).管道的读写规则c).匿名管道的特点 2.有名管道(FIFO)a).创建命名管道b).命名管道的特点c).基于命名管道的进程间通信#xff08;服务端/客户端#xff09; 三、消息队列四、共享内存1.什么是共享内存… 文章目录 一、什么是进程间通信二、管道1.匿名管道(pipe)a).创建匿名管道b).管道的读写规则c).匿名管道的特点 2.有名管道(FIFO)a).创建命名管道b).命名管道的特点c).基于命名管道的进程间通信服务端/客户端 三、消息队列四、共享内存1.什么是共享内存2.为什么要有共享内存a).mmap内存共享映射b). system V共享内存c).POSIX共享内存 五、信号量六、信号七、Socket总结 一、什么是进程间通信 进程通信就是指进程之间信息的传播和交换。 每个进程各自有不同的用户地址空间任何一个进程的全局变量在另一个进程中都看不到所以进程之间要交换数据必须通过内核在内核中开辟一块缓冲区进程1把数据从用户空间拷到内核缓冲区进程2再从内核缓冲区把数据读走内核提供的这种机制称为进程间通信IPCInterProcess Communication 进程间通信的目的 数据传输一个进程需要将它的数据发送给另一个进程发送的数据量在一个字节到几兆字节之间。共享数据多个进程想要操作共享数据一个进程对共享数据的修改别的进程应该立刻看到。通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。资源共享多个进程之间共享同样的资源。为了做到这一点需要内核提供锁和同步机制。进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。 System V IPC和 POSIX IPC的区别 当我们在 Linux 系统中进行进程间通信时例如信号量消息队列共享内存等方式会发现有System V以及POSIX两种类型。而它们的区别如下 Posix函数有下划线分隔SystemV函数没有是连在一起的Posix 每个IPC对象是有名称的而且名称是一个很重要的概念。mq_open sem_open shm_open三个函数的第一个参数就是这个名称这个名称不一定是在文件系统中存在的名称。 要使用IPC对象需要创建或者打开这与文件操作类似主要是使用mq_open、sem_open、shm_open 函数操作。在创建或者打开ipc对象时需要指定操作的mode例如O_RONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL 等IPC对象是有一定权限的与文件的权限类似。对应的SystemV每个IPC有一个重要的类型是key_t在msgget、semget、shmget函数操作中都需要利用这个类型是参数。系统中对每个ipc对象都会有一个结构体来标识POSIX 在无竞争条件下不需要陷入内核其实现是非常轻量级的; System V 则不同无论有无竞争都要执行系统调用因此性能落了下风。 二、管道 管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道” 1.匿名管道(pipe) a).创建匿名管道 如何创建一个匿名管道 我们通常采用下面的接口 #include unistd.h int pipe(int fd[2]);功能:创建一无名管道 参数:fd文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0失败返回错误代码这里表示创建一个匿名管道并返回了两个描述符一个是管道的读取端描述符 fd[0]另一个是管道的写入端描述符 fd[1]。注意这个匿名管道是特殊的文件只存在于内存不存于文件系统中。 其实所谓的管道就是内核里面的一段缓存。从管道的一段写入的数据实际上是缓存在内核中的另一端读取也就是从内核中读取这段数据。另外管道传输的数据是无格式的流且大小受限。 看到这你可能会有疑问了这两个描述符都是在一个进程里面并没有起到进程间通信的作用怎么样才能使得管道是跨过两个进程的呢 我们可以使用 fork 创建子进程创建的子进程会复制父进程的文件描述符这样就做到了两个进程各有两个fd[0] 与fd[1]两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。 管道只能一端写入另一端读出所以上面这种模式容易造成混乱因为父进程和子进程都可以同时写入也都可以读出。那么为了避免这种情况通常的做法是 父进程关闭读取的 fd[0]只保留写入的 fd[1]子进程关闭写入的 fd[1]只保留读取的 fd[0] 所以说如果需要双向通信则应该创建两个管道。 到这里我们仅仅解析了使用管道进行父子进程之间的通信但是在我们 shell 里面并不是这样的。 在 shell 里面执行 A | B命令的时候A 进程和 B 进程都是 shell 创建出来的子进程A 和 B 之间不存在父子关系它俩的父进程都是 shell。 所以说在 shell 里通过 | 匿名管道将多个命令连接在一起实际上也就是创建了多个子进程那么在我们编写 shell 脚本时能使用一个管道搞定的事情就不要多用一个管道这样可以减少创建子进程的系统开销。 b).管道的读写规则 当没有数据可读时 O_NONBLOCK disableread调用阻塞即进程暂停执行一直等到有数据来到为止。 O_NONBLOCK enableread调用返回-1errno值为EAGAIN。当管道满的时候 O_NONBLOCK disable write调用阻塞直到有进程读走数据 O_NONBLOCK enable调用返回-1errno值为EAGAIN如果所有管道写端的文件描述符被关闭则read返回0如果所有管道读端的文件描述符被关闭则write操作会产生信号SIGPIPE,进而可能导致write进程退出当要写入的数据量不大于PIPE_BUF时linux将保证写入的原子性。 当要写入的数据量大于PIPE_BUF时linux将不再保证写入的原子性。 c).匿名管道的特点 管道是半双工的数据只能向一个方向流动需要双方通信时需要建立起两个管道。只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);单独构成一种独立的文件系统管道对于管道两端的进程而言就是一个文件但它不是普通的文件它不属于某种文件系统而是自立门户单独构成一种文件系统并且只存在与内存中。数据的读出和写入一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾并且每次都是从缓冲区的头部读出数据。一般而言进程退出管道释放所以管道的生命周期随进程一般而言内核会对管道操作进行同步与互斥 而也可见匿名管道的局限性 只支持单向数据流只能用于具有亲缘关系的进程之间没有名字管道的缓冲区是有限的管道制存在于内存中在管道创建时为缓冲区分配一个页面大小管道所传送的是无格式字节流这就要求管道的读出方和写入方必须事先约定好数据的格式比如多少字节算作一个消息或命令、或记录等等 我们可以用下段代码来测试匿名管道的性质 #includestdlib.h #includestdio.h #includeiostream #includeunistd.h #includeassert.h #includesys/wait.h #includesys/types.h #includestring.husing namespace std;//子进程进行写入父进程进行读取 int main() {int fds[2];int n pipe(fds);assert(n0);pid_t id fork();assert(id0);if(id0)//子进程进行写入{close(fds[0]);//关闭读端const char* s 我是子进程我正在写入:;int cnt 0;while(true){char buffer[1024];snprintf(buffer,sizeof(buffer),child say-%s[%d]-[%d],s,getpid(),cnt);write(fds[1],buffer,strlen(buffer));sleep(1);}//close(fds[1]);//cout子进程关闭写端!\n;exit(0);}//父进程进行读取close(fds[1]);int c 10;while(c--){char buffer[1024];ssize_t s read(fds[0],buffer,sizeof(buffer)-1);if(s0){buffer[s] 0;coutpatent get message#buffer my pid is :getpid()endl;}else if(s0){coutread over!\n;break;}//break;}close(fds[0]);cout父进程关闭读端!\n;int status 0;n waitpid(id,status,0);assert(nid);coutpid-n:(status0x7f)endl;//读端关闭会向子进程发送SIGPIPE信号可能导致写端退出return 0; }当父进程关闭读端时write会产生SIGCHILD信号。 2.有名管道(FIFO) a).创建命名管道 命名管道可以从命令行上创建命令行方法是使用下面这个命令 mkfifo filename命名管道也可以从程序里创建相关函数有 int mkfifo(const char *filename,mode_t mode) 例如我们创建命名管道 int main(int argc, char *argv[]) {mkfifo(p2, 0644);return 0; }b).命名管道的特点 命名管道FIFO又叫有名管道和无名管道的主要区别在于命名管道有一个名字命名管道的名字对应于一个磁盘索引节点但没有数据块有了这个文件名任何进程有相应的权限都可以对它进行访问。通过mknode()系统调用或者mkfifo()函数来建立的。一旦建立任何进程都可以通过文件名将其打开和进行读写而不局限于父子进程当然前提是进程对FIFO有适当的访问权。当不再被进程使用时FIFO在内存中释放但磁盘节点仍然存在。可以使用open()函数通过文件名可以打开已经创建的命名管道而无名管道不能由open来打开。当一个命名管道不再被任何进程打开时它没有消失还可以再次被打开就像打开一个磁盘文件一样。可以用删除普通文件的方法将其删除实际删除的事磁盘上对应的节点信息。命名管道也是半双工的通信方式命名管道除了具有管道所具有的功能外它还允许无亲缘关系进程间的通信。 命名管道的打开规则 如果当前打开操作是为读而打开FIFO时 O_NONBLOCK disable阻塞直到有相应进程为写而打开该FIFO O_NONBLOCK enable立刻返回成功如果当前打开操作是为写而打开FIFO时 O_NONBLOCK disable阻塞直到有相应进程为读而打开该FIFO O_NONBLOCK enable立刻返回失败错误码为ENXIO c).基于命名管道的进程间通信服务端/客户端 当命名管道不再被任何进程打开时它没有消失还可以再次被打开所以它的生命周期不随进程和匿名管道不同所以我们使用完后得处理这个命名管道这些工作我们放在头文件中完成 #includeiostream #includeassert.h #includestring #includestring.h #includestdio.h #includestdlib.h #includesys/types.h #includesys/stat.h #includeunistd.h #includefcntl.h #includeerrno.h#define NAMED_PIPE mypipe.tmpint creatFifo(const std::string path) {umask(0);int n mkfifo(path.c_str(),0600); if(n0) return 1;else{std::couterror: errno err string strerror(errno)std::endl;return -1;} }void removeFifo(const std::string path) {int n unlink(path.c_str());assert(n0);(void)n; }我们用服务端来读取客户端发送的信息 #includecomm.hppint main() {int r creatFifo(NAMED_PIPE);//assert(r-1);//std::coutrstd::endl;(void)r;std::coutserver beginstd::endl;int rfd open(NAMED_PIPE,O_RDONLY);std::coutserver endstd::endl;if(rfd0){perror(open);exit(-2);}char buffer[1024];while(true){ssize_t n read(rfd,buffer,sizeof(buffer)-1);if(n0){buffer[n] 0;std::coutget mseeage-bufferstd::endl; }else if(n0){std::coutclient quit ! me too !std::endl;break;}else{perror(read);break;}}close(rfd);removeFifo(NAMED_PIPE);return 0; }我们用客户端来发送信息 #includecomm.hpp int main() {std::coutclient beginstd::endl;int wfd open(NAMED_PIPE,O_WRONLY);std::coutclient endstd::endl;if(wfd0){perror(open);exit(-1);}char buffer[1024];while(true){std::coutPlease say#;fgets(buffer,sizeof(buffer),stdin);if(strlen(buffer)0) buffer[strlen(buffer)-1] 0;ssize_t n write(wfd,buffer,strlen(buffer));assert(nstrlen(buffer));(void)n;}close(wfd);return 0; }而通信的过程如下 通过上述代码我们可以更好的理解命名管道它的操作如同一个磁盘上的文件。 三、消息队列 前面说到管道的通信方式是效率低的因此管道不适合进程间频繁地交换数据。 对于这个问题消息队列的通信模式就可以解决。比如A 进程要给 B 进程发送消息A 进程把数据放在对应的消息队列后就可以正常返回了B 进程需要的时候再去读取数据就可以了。同理B 进程要给 A 进程发送消息也是如此。 再来消息队列是保存在内核中的消息链表在发送数据时会分成一个一个独立的数据单元也就是消息体数据块消息体是用户自定义的数据类型消息的发送方和接收方要约定好消息体的数据类型所以每个消息体都是固定大小的存储块不像管道是无格式的字节流数据。 如果进程从消息队列中读取了消息体内核就会把这个消息体删除。 消息队列生命周期随内核如果没有释放消息队列或者没有关闭操作系统消息队列会一直存在而前面提到的匿名管道的生命周期是随进程的创建而建立随进程的结束而销毁。 消息这种模型两个进程之间的通信就像平时发邮件一样你来一封我回一封可以频繁沟通了。 但邮件的通信方式存在不足的地方有两点一是通信不及时二是附件也有大小限制这同样也是消息队列通信不足的点。 消息队列不适合比较大数据的传输因为在内核中每个消息体都有一个最大长度的限制同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中会有两个宏定义 MSGMAX 和 MSGMNB它们以字节为单位分别定义了一条消息的最大长度和一个队列的最大长度。 消息队列通信过程中存在用户态与内核态之间的数据拷贝开销因为进程写入数据到内核中的消息队列时会发生从用户态拷贝数据到内核态的过程同理另一进程读取内核中的消息数据时会发生从内核态拷 贝数据到用户态的过程。 四、共享内存 1.什么是共享内存 共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间提高程序效率。 共享内存是由IPC为一个进程创建的一个特殊的地址范围它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据所做的改动会立刻被有访问同一段共享内存的其他进程看到。 要注意的是共享内存本身没有提供任何同步功能。也就是说在第一个进程结束对共享内存的写操作之前并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号等。 2.为什么要有共享内存 使用文件或管道进行进程间通信会有很多局限性比如效率问题以及数据处理使用文件描述符而不如内存地址访问方便于是多个进程以共享内存的方式进行通信就成了很自然要实现的IPC方案。Linux系统在编程上为我们准备了多种手段的共享内存方案。包括 mmap内存共享映射。XSI共享内存。POSIX共享内存。 a).mmap内存共享映射 mmap用于将文件或设备映射到进程地址空间内 使得进程在进程内可以直接访问。基于该特性Linux系统用它实现多进程的内存共享功能 。其相关调用API原型如下 #include sys/mman.hvoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length);由于这个系统调用的特性可以用在很多场合所以Linux系统用它实现了很多功能并不仅局限于存储映射。在这主要介绍的就是用mmap进行多进程的内存共享功能。Linux产生子进程的系统调用是fork根据fork的语义以及其实现我们知道新产生的进程在内存地址空间上跟父进程是完全一致的。 所以Linux的mmap实现了一种可以在父子进程之间共享内存地址的方式其使用方法是 父进程将flags参数设置MAP_SHARED方式通过mmap申请一段内存。内存可以映射某个具体文件也可以不映射具体文件fd置为-1flag设置为MAP_ANONYMOUS。父进程调用fork产生子进程。之后在父子进程内都可以访问到mmap所返回的地址就可以共享内存了。 b). system V共享内存 考虑到mmap只适用于父子进程间内存共享的这一局限性为了满足无关进程间共享内存的需求Linux提供了更具通用性的手段System V (XSI)共享内存。就是我们常用的shmctl 相关调用 shmget函数 功能用来创建共享内存 原型int shmget(key_t key, size_t size, int shmflg); 参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样的 返回值成功返回一个非负整数即该共享内存段的标识码失败返回-1对于参数key_t key 的理解 一个key是通过ftok函数使用一个pathname和一个proj_jd产生的。就是说在一个可能会使用共享内存的项目组中大家可以约定一个文件名和一个项目的proj_id来在同一个系统中确定一段共享内存的key。ftok并不会去创建文件所以必须指定一个存在并且进程可以访问的pathname路径。这里还要指出的一点是ftok实际上并不是根据文件的文件路径和文件名pathname产生key的在实现上它使用的是指定文件的inode编号和文件所在设备的设备编号。所以不要以为你是用了不同的文件名就一定会得到不同的key因为不同的文件名是可以指向相同inode编号的文件的硬连接。也不要认为你是用了相同的文件名就一定可以得到相同的key在一个系统上同一个文件名会被删除重建的几率是很大的这种行为很有可能导致文件的inode变化。所以一个ftok的执行会隐含stat系统调用也就不难理解了。 #include sys/types.h #include sys/ipc.hkey_t ftok(const char *pathname, int proj_id); proj_id是可以根据自己的约定随意设置。这个数字,有的称之为project ID; 在UNIX系统上,它的取值是1到255;shmat函数 功能将共享内存段连接到进程地址空间 原型void *shmat(int shmid, const void *shmaddr, int shmflg); 参数shmid: 共享内存标识shmaddr:指定连接的地址shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY返回值成功返回一个指针指向共享内存第一个字节失败返回-1shmdt函数 功能将共享内存段与当前进程脱离 原型int shmdt(const void *shmaddr); 参数shmaddr: 由shmat所返回的指针 返回值成功返回0失败返回-1 注意将共享内存段与当前进程脱离不等于删除共享内存段 共享内存由于其特性与进程中的其他内存段在使用习惯上有些不同。一般进程对栈空间分配可以自动回收而堆空间通过malloc申请free回收。这些内存在回收之后就可以认为是不存在了。但是共享内存不同用shmdt之后实际上其占用的内存还在并仍然可以使用shmat映射使用。如果不是用shmctl或ipcrm命令删除的话那么它将一直保留直到系统被关闭。对于刚接触共享内存的程序员来说这可能需要适应一下。实际上共享内存的生存周期根文件更像进程对文件描述符执行close并不能删除文件而只是关闭了本进程对文件的操作接口这就像shmdt的作用。而真正删除文件要用unlink活着使用rm命令这就像是共享内存的shmctl的IPC_RMID和ipcrm命令。当然文件如果不删除下次重启依旧还在因为它放在硬盘上而共享内存下次重启就没了因为它毕竟还是内存。 shmctl函数 功能用于控制共享内存 原型int shmctl(int shmid, int cmd, struct shmid_ds *buf); 参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作有三个可取值buf:指向一个保存着共享内存的模式状态和访问权限的数据结构 返回值成功返回0失败返回-1struct shmid_ds {struct ipc_perm shm_perm; /* Ownership and permissions */size_t shm_segsz; /* Size of segment (bytes) */time_t shm_atime; /* Last attach time */time_t shm_dtime; /* Last detach time */time_t shm_ctime; /* Last change time */pid_t shm_cpid; /* PID of creator */pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */shmatt_t shm_nattch; /* No. of current attaches *//* ... */ };这些函数的使用如下 #define MAX_SIZE 4048 #define PROJ_ID 0x66 #define PATHNAME . //系统分配共享内存是以4kb为单位的int main() {//参数1共享内存的文件名 参数2文件的权限//成功返回key失败返回-1key_t key ftok(PATHNAME,PROJ_ID);//std::coutkeystd::endl;//参数1key值 参数2共享内存的最大空间 参数3IPC_CREAT/IPC_EXCL//成功返回shm的id,失败返回-1错误码被设置int id shmget(key,MAX_SIZE,IPC_CREAT);//参数1shm的id 参数2const void* shmaddr 参数3int shmflg //成功返回shm的address 失败返回(void*)-1 错误码被设置以标识错误//参数为NULL核心自动选择一个地址 char* addr (char*)shmat(id,NULL,0);//参数由shmat返回的指针//成功返回0失败返回-1错误码被设置int ret shmdt(addr);//用于控制共享内存//参数2将要采取的动作 IPC_STAT/IPC_SET/IPC_RMID(删除共享内存段)//int shmctl(int shmid,int cmd,struct shmid_ds *buf);return 0; }我们同样可以根据共享内存实现一个客户端和服务端的通信程序不过不在此赘述了! c).POSIX共享内存 POSIX共享内存实际上毫无新意它本质上就是mmap对文件的共享方式映射只不过映射的是tmpfs文件系统上的文件。 什么是tmpfsLinux提供一种“临时”文件系统叫做tmpfs它可以将内存的一部分空间拿来当做文件系统使用使内存空间可以当做目录文件来用。现在绝大多数Linux系统都有一个叫做/dev/shm的tmpfs目录就是这样一种存在。 POSIX共享内存使用方法有以下两个步骤 通过shm_open创建或打开一个POSIX共享内存对象调用mmap将它映射到当前进程的地址空间 五、信号量 用了共享内存通信方式带来新的问题那就是如果多个进程同时修改同一个共享内存很有可能就冲突了。例如两个进程都同时写一个地址那先写的那个进程会发现内容被别人覆盖了。 为了防止多进程竞争共享资源而造成的数据错乱所以需要保护机制使得共享的资源在任意时刻只能被一个进程访问。正好信号量就实现了这一保护机制。 信号量其实是一个整型的计数器主要用于实现进程间的互斥与同步而不是用于缓存进程间通信的数据。 信号量表示资源的数量控制信号量的方式有两种原子操作 一个是 P 操作这个操作会把信号量减去 1相减后如果信号量 0则表明资源已被占用进程需阻塞等待相减后如果信号量 0则表明还有资源可使用进程可正常继续执行。另一个是 V 操作这个操作会把信号量加上 1相加后如果信号量 0则表明当前有阻塞中的进程于是会将该进程唤醒运行相加后如果信号量 0则表明当前没有阻塞中的进程 P 操作是用在进入共享资源之前V 操作是用在离开共享资源之后这两个操作是必须成对出现的。 接下来举个例子如果要使得两个进程互斥访问共享内存我们可以初始化信号量为 1。 具体的过程如下 进程 A 在访问共享内存前先执行了 P 操作由于信号量的初始值为 1故在进程 A 执行 P 操作后信号量变为 0表示共享资源可用于是进程 A 就可以访问共享内存。若此时进程 B 也想访问共享内存执行了 P 操作结果信号量变为了 -1这就意味着临界资源已被占用因此进程 B 被阻塞。直到进程 A 访问完共享内存才会执行 V 操作使得信号量恢复为 0接着就会唤醒阻塞中的线程 B使得进程 B 可以访问共享内存最后完成共享内存的访问后执行 V 操作使信号量恢复到初始值 1。 可以发现信号初始化为 1就代表着是互斥信号量它可以保证共享内存在任何时刻只有一个进程在访问这就很好的保护了共享内存。 另外在多进程里每个进程并不一定是顺序执行的它们基本是以各自独立的、不可预知的速度向前推进但有时候我们又希望多个进程能密切合作以实现一个共同的任务。 例如进程 A 是负责生产数据而进程 B 是负责读取数据这两个进程是相互合作、相互依赖的进程 A 必须先生产了数据进程 B 才能读取到数据所以执行是有前后顺序的。 那么这时候就可以用信号量来实现多进程同步的方式我们可以初始化信号量为 0。 具体过程 如果进程 B 比进程 A 先执行了那么执行到 P 操作时由于信号量初始值为 0故信号量会变为 -1表示进程 A 还没生产数据于是进程 B 就阻塞等待接着当进程 A 生产完数据后执行了 V 操作就会使得信号量变为 0于是就会唤醒阻塞在 P 操作的进程 B最后进程 B 被唤醒后意味着进程 A 已经生产了数据于是进程 B 就可以正常读取数据了。 可以发现信号初始化为 0就代表着是同步信号量它可以保证进程 A 应在进程 B 之前执行。 对于信号量的更多细节在后面的文章中会详细介绍 六、信号 上面说的进程间通信都是常规状态下的工作模式。对于异常情况下的工作模式就需要用信号的方式来通知进程。 信号跟信号量虽然名字相似度 66.66%但两者用途完全不一样就好像 Java 和 JavaScript 的区别。 在 Linux 操作系统中 为了响应各种各样的事件提供了几十种信号分别代表不同的意义。我们可以通过 kill -l 命令查看所有的信号 运行在 shell 终端的进程我们可以通过键盘输入某些组合键的时候给进程发送信号。例如 CtrlC 产生 SIGINT 信号表示终止该进程CtrlZ 产生 SIGTSTP 信号表示停止该进程但还未结束 如果进程在后台运行可以通过 kill 命令的方式给进程发送信号但前提需要知道运行中的进程 PID 号例如 kill -9 1050 表示给 PID 为 1050 的进程发送 SIGKILL 信号用来立即结束该进程 所以信号事件的来源主要有硬件来源如键盘 CltrC 和软件来源如 kill 命令。 信号是进程间通信机制中唯一的异步通信机制因为可以在任何时候发送信号给某一进程一旦有信号产生我们就有下面这几种用户进程对信号的处理方式。 1.执行默认操作。Linux 对每种信号都规定了默认操作例如上面列表中的 SIGTERM 信号就是终止进程的意思。 2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时我们就执行相应的信号处理函数。 3.忽略信号。当我们不希望处理某些信号的时候就可以忽略该信号不做任何处理。有两个信号是应用进程无法捕捉和忽略的即 SIGKILL 和 SEGSTOP它们用于在任何时候中断或结束某一进程。 对于信号的更多细节我们会在下一篇文章中介绍 七、Socket 前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信那要想跨网络与不同主机上的进程之间通信就需要 Socket 通信了。 实际上Socket 通信不仅可以跨网络与不同主机的进程间通信还可以在同主机上进程间通信。 我们来看看创建 socket 的系统调用 三个参数分别代表 domain 参数用来指定协议族比如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机type 参数用来指定通信特性比如 SOCK_STREAM 表示的是字节流对应 TCP、SOCK_DGRAM 表示的是数据报对应 UDP、SOCK_RAW 表示的是原始套接字protocal 参数原本是用来指定通信协议的但现在基本废弃。因为协议已经通过前面两个参数指定完成protocol 目前一般写成 0 即可 根据创建 socket 类型的不同通信的方式也就不同 实现 TCP 字节流通信 socket 类型是 AF_INET 和 SOCK_STREAM实现 UDP 数据报通信socket 类型是 AF_INET 和 SOCK_DGRAM实现本地进程间通信 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外AF_UNIX 和 AF_LOCAL 是等价的所以 AF_UNIX 也属于本地 socket 而关于socket的更多细节我们在后面的文章会介绍 总结 由于每个进程的用户空间都是独立的不能相互访问这时就需要借助内核空间来实现进程间通信原因很简单每个进程都是共享一个内核空间。 Linux 内核提供了不少进程间通信的方式其中最简单的方式就是管道管道分为「匿名管道」和「命名管道」。 匿名管道顾名思义它没有名字标识匿名管道是特殊文件只存在于内存没有存在于文件系统中shell 命令中的 | 竖线就是匿名管道通信的数据是无格式的流并且大小受限通信的方式是单向的数据只能在一个方向上流动如果要双向通信需要创建两个管道再来匿名管道是只能用于存在父子关系的进程间通信匿名管道的生命周期随着进程创建而建立随着进程终止而消失。 命名管道突破了匿名管道只能在亲缘关系进程间的通信限制因为使用命名管道的前提需要在文件系统创建一个类型为 p 的设备文件那么毫无关系的进程就可以通过这个设备文件进行通信。另外不管是匿名管道还是命名管道进程写入的数据都是缓存在内核中另一个进程读取数据时候自然也是从内核中获取同时通信数据都遵循先进先出原则不支持 lseek 之类的文件定位操作。 消息队列克服了管道通信的数据是无格式的字节流的问题消息队列实际上是保存在内核的「消息链表」消息队列的消息体是可以用户自定义的数据类型发送数据时会被分成一个一个独立的消息体当然接收数据时也要与发送方发送的消息体的数据类型保持一致这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。 共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销它直接分配一个共享空间每个进程都可以直接访问就像访问进程自己的空间一样快捷方便不需要陷入内核态或者系统调用大大提高了通信的速度享有最快的进程间通信方式之名。但是便捷高效的共享内存通信带来新的问题多进程竞争同个共享资源会造成数据的错乱。 那么就需要信号量来保护共享资源以确保任何时刻只能有一个进程访问共享资源这种方式就是互斥访问。信号量不仅可以实现访问的互斥性还可以实现进程间的同步信号量其实是一个计数器表示的是资源个数其值可以通过两个原子操作来控制分别是 P 操作和 V 操作。 与信号量名字很相似的叫信号它俩名字虽然相似但功能一点儿都不一样。信号是异步通信机制信号可以在应用进程和内核之间直接交互内核也可以利用信号来通知用户空间的进程发生了哪些系统事件信号事件的来源主要有硬件来源如键盘 CltrC 和软件来源如 kill 命令一旦有信号发生进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号 。有两个信号是应用进程无法捕捉和忽略的即 SIGKILL 和 SIGSTOP这是为了方便我们能在任何时候结束或停止某个进程。 前面说到的通信机制都是工作于同一台主机如果要与不同主机的进程间通信那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信还可以用于本地主机进程间通信可根据创建 Socket 的类型不同分为三种常见的通信方式一个是基于 TCP 协议的通信方式一个是基于 UDP 协议的通信方式一个是本地进程间通信方式。 以上就是进程间通信的主要机制了。
http://www.hkea.cn/news/14324860/

相关文章:

  • 网站游戏网站怎么建设游戏开科技的软件
  • 怎样更换网站cms加强网站队伍建设
  • 响应式网站的优缺点园林景观设计公司设备列表
  • 实用网站设计步骤微网站 杭州
  • 哈尔滨网站公司哪家好提出网站推广途径
  • 大连鼎信网站建设公司地址wordpress过期
  • 做网站卖链接中标公告 网站建设
  • 怎么做资源类网站淄博网站设计方案
  • 怎么样做外贸网站可以做婚礼鲜花布置的网站
  • 360免费建站视频wordpress插件商品对比
  • 免费模板网站制作网页制作公司的渠道通路
  • app公司定制开发asp模版网站如何做优化
  • 代理公司网站备案京东联盟怎么推广赚钱
  • 开封网站建设哪家好网站规划建设与管理维护课后答案6
  • iis发布网站后无法加载dll网站的详情页面设计
  • 重庆的seo服务公司优化大师安卓版
  • 如何规范网站使用微信软文范例大全100
  • 泉州网站建设公司招聘销售小程序商店图标
  • 郑州高端品牌网站建设营销策划包括哪些内容
  • 小说网站建设目的目前最好用的网络管理软件
  • 有没有设计网站在广州的网站已运行时间代码
  • 建设网站地图素材深圳建设局网站打不开
  • 嘉兴门户网站建设网站外包制作
  • 网站开发定制合同范本建一个网站需要购买域名 虚拟主机
  • 专做女装拿货的网站四川做网站优化价格
  • 安达市建设局网站国内做任务得数字货币的网站
  • 微信公众平台开发商宁波seo哪家最便宜
  • 网站建设策划文案企业邮箱怎么申请免费的
  • 网站导航栏兼容性建设银行官网招聘网站
  • 哪个网站上做ppt比较好看的邢台是不是又封了