搭建网站服务器,企业为什么要做网站运营,分享推广,北京互联网网站建设现在再Linux应用较多的进程间通信方式主要有以下几种#xff1a; 1#xff09;无名管道#xff08;pipe#xff09;及有名管道#xff08;fifo#xff09;#xff1a;无名管道可用于具有亲缘关系进程间的通信#xff1b;有名管道除具有管道相似的功能外#xff0c;它还… 现在再Linux应用较多的进程间通信方式主要有以下几种 1无名管道pipe及有名管道fifo无名管道可用于具有亲缘关系进程间的通信有名管道除具有管道相似的功能外它还允许无亲缘关系进程使用 2信号signal信号是在软件层次上对中断机制的一种模拟它是比较复杂的通信方式用于通知进程某事件发生。一个进程收到一个信号与处理器收到一个中断请求处理的过程类似 3消息队列message queue消息队列是消息的链接表包括POSIX消息队列和System V 消息队列。它克服了前两种通信方式中信息量有限的缺点。具有写权限的进程可以按照一定的规则向消息队列中添加新消息对消息队列有读权限的进程则可以从消息队列中读取消息。 4共享内存shared memory可以说这时最有效的进程间通信方式。它使得多个进程可以访问同一块内存空间不同进程可以及时查看对方进程中对共享数据的更新。这种通信方式需要依靠某种同步机制如互斥锁和信号量等。 5信号量semaphore主要作为进程之间以及统一进程的不同线程之间的同步和互斥手段。 6套接字socket这时一种使用更广泛的进程间通信机制它可用于网络中不同主机之间的进程间通信应用非常广泛。 管道通信 管道是Linux 中进程间通信的一种方式它把一个程序的输出直接连接到另一个程序的输入Linux 的管道主要包括两种无名管道和有名管道。 一、无名管道 无名管道是Linux中管道通信的一种原始方法他有如下特点 1只能用于具有亲缘关系的进程之间的通信也就是父子进程或兄弟进程之间 2是一个单工的通信模式具有固定的读端和写端 3管道也可以看成一种特殊的文件对于它的读写也可是使用普通的read() 、write()等函数但是它不属于任何文件系统并且只存在于内存中其字节大小为0 1、无名管道的创建与关闭 无名管道是基于文件描述符的通信方式。当一个管道创建时它会创建两个文件描述符fd[0] 、fd[1] 。其中 fd[0] 固定用于读管道而 fd[1] 固定用于写管道如下图这样就构成了一个单向的数据通道 管道关闭时只需要用 close() 函数将这两个文件描述符关闭即可。 2、管道创建函数 创建管道可以通过 pipe() 来实现其语法如下 所需头文件#include unistd.h函数原型int pipe(int fd[]);函数传入值fd 包含两个元素的整型数组存放管道对应的文件描述符函数返回值成功0 出错-1 3、管道读写说明 用pipe() 函数创建的管道两端处于一个进程中。由于管道主要是用于不同进程间的通信通常是先创建一个管道再调用 fork () 函数创建一个子进程该子进程会继承父进程所创建的管道。 需要注意的是无名管道是单工的工作方式即进程要么只能读管道要么只能写管道。父子进程虽然都拥有管道的读端和写端但是只能使用其中一个例如可以约定父进程读管道而子进程写管道。这样就应该把不使用的读端或写端文件描述符关闭。 例如如果将父进程的写端 fd[1] 和子进程的读端 fd[0] 关闭。此时父子进程之间就建立了一条“子进程写入 父进程读取”的通道。同样也可以关闭父进程的 fd[0] 和子进程的fd[1] 这样就可以建立一条“父进程写入子进程读取”的通道。另外父进程也可以创建 多个子进程各个子进程都继承了管道的fd[0] 和 fd[1] 这样就建立子进程之间的数据通道。 4、管道读写注意 1只有管道的读端存在时向管道写入数据才有意义否则向管道中写入数据的进程将收到内核传来的 SIGPIPE 信号 通常为Broken Pipea错误。 2向管道写入数据时Linux 将不保证写入的原子性 , 管道缓冲区只要有空间写进程就会试图向管道写入数据。如果管道缓冲区已满那么写操作将一直阻塞。 3父进程在运行时它们的先后次序必不能保证。为了确保父子进程已经关闭了相应的文件描述符可在两个进程中调用 sleep() 函数当然用互斥和同步会更好 下面是一个实例 [cpp] view plaincopy #include stdio.h #include unistd.h #include string.h #include stdlib.h int pid,pid1,pid2; int main(int argc, const char *argv[]) { int fd[2]; char outpipe[100],inpipe[100]; if(pipe(fd) 0) { perror(pipe error!); return -1; } if((pid1 fork()) 0) { perror(fork pid1 error); return -1; } else if(pid1 0) { printf(Child1s pid is %d\n,getpid()); close(fd[0]); strcpy(outpipe,Child 1 is sending a message!); if(write(fd[1],outpipe,50) -1) { perror(Child 1 write to outpipe error); return -1; } exit(0); } if((pid2 fork()) 0) { perror(fork pid2 error); return -1; } else if(pid2 0) { printf(Child2s pid is %d\n,getpid()); close(fd[0]); strcpy(outpipe,Child 2 is sending a message!); sleep(1); if(write(fd[1],outpipe,50) -1) { perror(Child 2 write to outpipe error); return -1; } exit(0); } close(fd[1]); pid wait(NULL); printf(%d process is over!\n,pid); if(read(fd[0],inpipe,50) -1) { perror(read Child1 pipe error); return -1; } printf(%s\n,inpipe); pid wait(NULL); //回收第二个结束的子进程 printf(%d process is over!\n,pid); if(read(fd[0],inpipe,50) -1) { perror(read Child1 pipe error); return -1; } printf(%s\n,inpipe); return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/pipe$ ./pipe Child2s pid is 8504 Child1s pid is 8503 8503 process is over! Child 1 is sending a message! 8504 process is over! Child 2 is sending a message! fsubuntu:~/qiang/pipe$ 二、有名管道有名管道FIFO是对无名管道的一种改进它具有如下特点 1它可以使互不相关的两个进程实现彼此通信 2该管道可以通过路径名来指出并且在文件系统中是可见的。在建立了管道之后两个进程就可以把它当做普通文件一样进行读写操作使用非常方便 3FIFO严格地遵循先进先出规则对管道及 FIFO 的读总是从开始处返回数据对它们的写则把数据添加到末尾。有名管道不支持如lseek()等文件定位操作 有名管道FIFO的创建可以使用 mkfifo() 函数该函数类似文件中的open() 操作可以指定管道的路径和访问权限 用户也可以在命令行使用 “mknod 管道名”来创建有名管道。 在创建管道成功以后就可以使用open()、read() 和 write() 这些函数了。与普通文件一样对于为读而打开的管道可在 open() 中设置 O_RDONLY对于为写而打开的管道可在 open() 中设置O_WRONLY。 1、对于读进程 缺省情况下如果当前FIFO内没有数据读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。 2、对于写进程 只要FIFO有空间数据就可以被写入。若空间不足写进程会阻塞知道数据都写入为止 mkfifo() 函数语法如下 所需头文件#include sys/types.h #include sys/state.h函数原型 int mkfifo( const char *filename,mode_t mode)参数mode管道的访问权限函数返回值成功0 出粗-1 下面是个实例来学习有名管道的使用 create.c [cpp] view plaincopy #include stdio.h #include stdlib.h #include sys/stat.h #include string.h #include errno.h int main(int argc,char *argv[]) { if(argc 2) { printf(Usage:%s filename,argv[0]); return -1; } if(mkfifo(argv[1],0664) 0) { perror(mkfifo fails); exit(-1); } return 0; } write_fifo.c [cpp] view plaincopy #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/stat.h #include fcntl.h #include errno.h #define BUFFER_SIZE 1024 int main(int argc, const char *argv[]) { int fd; if(argc 2) { printf(Usage:%s filename,argv[0]); return -1; } if((fd open(argv[1],O_WRONLY)) 0) { perror(open error); exit(-1); } printf(open fifo %s for writing success!\n,argv[0]); char buffer[BUFFER_SIZE]; ssize_t n; while(fgets(buffer,BUFFER_SIZE,stdin)) { if((n write(fd,buffer,strlen(buffer))) -1) { perror(write fails); break; } } return 0; } read_fifo.c [cpp] view plaincopy #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/stat.h #include fcntl.h #include errno.h #define BUFFER_SIZE 1024 int main(int argc, const char *argv[]) { int fd; if(argc 2) { printf(Usage:%s filename,argv[0]); return -1; } if((fd open(argv[1],O_RDONLY)) 0) { perror(open error); exit(-1); } printf(open fifo %s for reading success!\n,argv[0]); char buffer[BUFFER_SIZE]; ssize_t n; while(1) { if((n read(fd,buffer,BUFFER_SIZE)) -1) { perror(read fails); return -1; } else if(n 0) { printf(peer close fifo\n); break; } else { buffer[n] \0; printf(read %d bytes from fifo:%s\n,n,buffer); } } return 0; } 执行结果如下 写端 [cpp] view plaincopy fsubuntu:~/qiang/fifo$ ./create_fifo tmp fsubuntu:~/qiang/fifo$ ./write_fifo tmp open fifo ./write_fifo for writing success! xiao zhi qiang ^C fsubuntu:~/qiang/fifo$ 读端 [cpp] view plaincopy fsubuntu:~/qiang/fifo$ ./read_fifo tmp open fifo ./read_fifo for reading success! read 5 bytes from fifo:xiao read 4 bytes from fifo:zhi read 6 bytes from fifo:qiang peer close fifo fsubuntu:~/qiang/fifo$ 这里执行时可以看到单独打开读或写二者会一直阻塞直到都打开才会打印第一句话当写端关闭时读端也会停止。 三、信号通信 信号是在软件层次上对中断机制的一种模拟。在原理上一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的一个进程不必通过任何操作在等待信号的到达。事实上进程也不知道信号到底什么时候到达。事实上进程也不知道信号到底什么时候到达。信号可以直接进行用户空间进程和内核进程之间的交互内核进程也可以利用它来通知用户空间进程发生了那些系统事件。它可以在任何时候发给某一进程而无需知道该进程的状态。如果该进程当前并未处于执行态则该信号就由内核保存起来知道该进程回恢复行再传递给它为止如果一个信号被进程设置为阻塞则该信号的传递被延迟直道阻塞被取消时才被传递给进程。 1、信号的生存周期 2、进程可以通过3种方式来响应一个信号 1忽略信号 即对信号不做任何处理其中有两个信号不能忽略SIGKILL及 SIGSTOP 2捕捉信号 定义信号处理函数当信号发生时执行相应的处理函数。 3执行默认操作 Linux 对每种信号都规定了默认操作后面会给出信号列表 这里介绍几个常用的信号
信号名含义默认操作SIGINT该信号在用户输入INTR字符通常是Ctrl C时发出 终端驱动程序发送该信号并送到前台进程中的每一个进程终止进程SIGQUIT该信号和SIGINT类似但由QUIT字符通常是Ctrl \来 控制终止进程SIGKILL该信号用来立即结束程序的运行 不能被阻塞、处理和忽略终止进程SIGALARM该信号当一个定时器到时的时候发出终止进程SIGSTOP该信号用于暂停一个进程 不能被阻塞、处理和忽略暂停进程SIGTSTP该信号用于交互停止进程挂起由Ctrl Z 来发出终止进程 3、信号处理流程 下面是内核如何实现信号机制即内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信号的反应、内核在什么实际处理和怎样处理进程收到的信号。 内核对信号的基本处理方法 内核给一个进程发送软中断信号的方法是在进程所在的进程表项的信号域设置对于该信号的位内核通过在进程的 struct task_struct 结构中的信号域中设置相应的位来实现向一个进程发送信号。这里要补充的是如果信号发送给一个正在睡眠的进程那么要看该进程进入睡眠的优先级如果进程睡眠在可被中断的优先级上则唤醒进程否则仅设置进程表中信号域相应的位而不唤醒进程。这一点比较重要因为进程检查是否收到信号的时机是一个进程在即将从内核态返回到用户态时或者在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。 内核处理一个进程收到的信号的时机是一个进程从内核态返回用户态时。所以当一个进程在内核态运行时软中断信号并不立即起作用要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态进程在用户态下不会有未处理完的信号。 内核处理一个进程收到的软中断信号是在该进程的上下文中因此进程必须处于运行状态。处理信号有三种类型进程接收到信号后退出进程忽略该信号进程收到信号后执行用户自定义的使用系统调用signal() 注册的函数。当进程接收到一个它忽略的信号时进程丢弃该信号就像从来没有收到该信号似得而继续运行。如果进程收到一个要捕捉的信号那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙内核是在用户栈上创建一个新的层该层中将返回地址的值设置成用户定义的处理函数的地址这样进程从内核返回弹出栈顶时就返回到用户定义的处理函数处从函数返回再弹出栈顶时才返回原来进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行如果用户定义的函数在内核态下运行的话用户就可以获得任何权限。 在信号的处理方法中有几点特别要引起注意 1在一些系统中当一个进程处理完中断信号返回用户态之前内核清除用户区中设定的对该信号的处理例程的地址即下一次进程对该信号的处理方法又改为默认值除非在下一次信号到来之前再次调用 signal() 系统调用。这可能会使得进程在调用 signal() 之前又得到该信号而导致退出。在BSD系统中内核不再清除该地址。但不清楚该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢出。为了避免出现上述情况。在BSD中内核模拟了对硬件中断的处理方法即在处理某个中断时阻止接收新的该类中断。 4、信号相关函数 1信号发送kill() 和 raise() kill() 函数同读者熟知的kill 系统命令一样可以发送信号给进程或进程组实际上kill 系统命令就是由 kill () 函数实现的。需要注意的是它不仅可以终止进程也可以向进程发送其他信号 与kill() 函数不同的是raise() 函数只允许进程向自身发送信号 kill() 函数语法 所需头文件#include signal.h #include sys/types.h函数原型int kill(pid_t pid,int sig);函数传入值pid 为正数 发送信号给进程号为pid 的进程 pid 为 0 信号被发送到所有和当前进程在同一个进程组的进程 pid 为 -1 信号发送给所有进程表中的进程除了进程号最大的进程外 pid 为 -1 信号发送给进程组号为 -pid 的每一个进程 sig 信号类型函数返回值成功 0 出错 -1 raise() 函数的语法 所需头文件#include signal.h #include sys/types.h函数原型int raise(int sig)函数传入值sig 信号类型函数返回值成功0 出错 -1 这里 raise() 等价于 kill ( getpid() , sig) 下面举一个实例 [cpp] view plaincopy #include stdio.h #include stdlib.h #include signal.h #include sys/types.h #include sys/wait.h #include unistd.h int main(int argc, char *argv[]) { pid_t pid; int ret; if((pid fork()) 0) { perror(fork error); exit(-1); } if(pid 0) { printf(child(pid : %d)is waiting for any signal\n,getpid()); raise(SIGSTOP); exit(0); } sleep(1); if((waitpid(pid,NULL,WNOHANG)) 0) { kill(pid,SIGKILL); printf(parent kill child process %d\n,pid); } waitpid(pid,NULL,0); return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/signal$ ./kill child(pid : 9977)is waiting for any signal parent kill child process 9977 fsubuntu:~/qiang/signal$ 2)、定时器信号alarm() 、pause()alarm() 也称闹钟信号它可以在进程中设置一个定时器。当定时器指定的时间到时它就向进程发送SIGALRAM信号。要注意的是一个进程只能有一个闹钟时间如果在调用alarm()函数之前已设置过闹钟信号则任何以前的闹钟时间都被新值所代替。 pause(函数是用于将调用进程挂起直至收到信号为止。 alarm()函数语法 所需头文件#include unistd.h函数原型unsigned int alarm(unsigned int second);函数传入值seconds指定秒数系统经过seconds秒之后向该进程发送SIGALARM信号函数返回值成功如果调用次alarm()前进程中已经设置了闹钟时间 则返回上一个闹钟剩余的时间否则返回 0 出错 -1 pause() 函数语法 所需头文件#include unistd.h函数原型int pause(void);函数返回值-1并且把 errno值设为RINTR 下面一个实例完成一个简单的sleep() 函数的功能由于SIGALARM 默认的系统动作为终止该进程因此程序在打印信息之前就已经结束了 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/signal$ ./alarm Alarm clock fsubuntu:~/qiang/signal$ 可以看到printf() 里面的内容并没有被打印 Alarm clock 是SIGALARM信号默认处理函数打印。 3)、信号的设置 signal() 和 sigaction() signal() 函数 要对一个信号进行处理就需要给出此信号发生时系统所调用的处理函数。可以为一个特定的信号除去无法捕捉的SIGKILL和SIGSTOP信号注册相应的处理函数。如果正在运行的程序源代码里注册了针对某一特定信号的处理程序不论当时程序执行到何处一旦进程接收到该信号相应的调用就会发生。 signal()函数使用时只需要指定的信号类型和信号处理函数即可。它主要用于前32种非实时信号的处理不支持信号传递信息。 其语法格式如下 所需头文件#include signal.h函数原型typeef void (*sighandle_t)(int) ; 函数指针类型 sighandle_t signal(int signum,sighandle_t handler);函数传入值signum指定信号代码 HandlerSIG_IGN忽略该信号 SIG_DFL采用系统默认方式处理信号 自定义的信号处理函数函数返回值成功以前的信号处理函数 出错-1 该函数第二个参数和返回值类型都是指向一个无返回值并且带一个整型参数的函数的指针且只要signal() 调用了自定义的信号处理函数即使这个函数什么也不做这个进程也不会被终止 下面一个程序利用signal来实现发送信号和接受信号的原理 程序内容创建子进程代表售票员父进程代表司机同步过程如下 售票员捕捉 SIGINT(代表开车)发送信号SIGUSR1给司机司机打印“lets gogogo!” 售票员捕捉 SIGQUIT(代表停止)发送信号SIGUSR2给司机司机打印“stop the bus!” 司机捕捉 SIGTSTP 代表车到总站发SIGUSR1给售票员售票员打印“Please get off the bus” 代码如下 [cpp] view plaincopy #include stdio.h #include stdlib.h #include unistd.h #include signal.h #include sys/types.h pid_t pid; void driver_handler(int signo); void saler_handler(int signo); int main(int argc,char *argv[]) { if((pid fork()) 0) { perror(fork error); return -1; } if(pid 0) { signal(SIGTSTP,driver_handler); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); signal(SIGUSR1,driver_handler); signal(SIGUSR2,driver_handler); while(1) pause(); } if(pid 0) { signal(SIGINT,saler_handler); signal(SIGTSTP,SIG_IGN); signal(SIGQUIT,saler_handler); signal(SIGUSR1,saler_handler); signal(SIGUSR2,SIG_IGN); while(1) pause(); } return 0; } void driver_handler(int signo) { if(signo SIGUSR1) printf(Lets gogogo!\n); if(signo SIGUSR2) printf(Stop the bus!\n); if(signo SIGTSTP) kill(pid,SIGUSR1); } void saler_handler(int signo) { pid_t ppid getppid(); if(signo SIGINT) kill(ppid,SIGUSR1); if(signo SIGQUIT) kill(ppid,SIGUSR2); if(signo SIGUSR1) { printf(please get off the bus\n); kill(ppid,SIGKILL); exit(0); } } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/signal$ ./signal ^CLets gogogo! ^\Stop the bus! ^CLets gogogo! ^\Stop the bus! ^CLets gogogo! ^\Stop the bus! ^CLets gogogo! ^\Stop the bus! ^Zplease get off the bus Killed fsubuntu:~/qiang/signal$ sigaction() 函数 sigaction() 函数的功能是检查或修改或两者与指定信号相关联的处理动作此函数可以完全代替signal 函数。 函数原型如下: 所需头文件#include signal.h函数原型int sigaction(int signum, const struct sigaction *act , struct sigaction *oldact );函数传入值signum可以指定SIGKILL和SIGSTOP以外的所有信号 act act 是一个结构体里面包含信号处理函数的地址、 处理方式等信息 oldact 参数oldact 是一个传出参数sigaction 函数调用成功后 oldact 里面包含以前对 signum 信号的处理方式的信息函数返回值成功0 出错-1 其中参数signo 是要检测或修改其具体动作的信号编号。若act 指针非NULL则要修改其动作。如果oact 指针非空则系统经由 oact 指针返回该信号的上一个动作 参数结构sigaction定义如下 [cpp] view plaincopy struct sigaction { void (*sa_handler) (int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); } ① sa_handler此参数和signal()的参数handler相同此参数主要用来对信号旧的安装函数signal()处理形式的支持 ② sa_sigaction新的信号安装机制处理函数被调用的时候不但可以得到信号编号而且可以获悉被调用的原因以及产生问题的上下文的相关信息。 ③ sa_mask用来设置在处理该信号时暂时将sa_mask指定的信号搁置 ④ sa_restorer 此参数没有使用 ⑤ sa_flags用来设置信号处理的其他相关操作下列的数值可用。可用OR 运算|组合 ŸA_NOCLDSTOP:如果参数signum为SIGCHLD则当子进程暂停时并不会通知父进程 SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前将此信号处理方式改为系统预设的方式 SA_RESTART:被信号中断的系统调用会自行重启 SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来 SA_SIGINFO信号处理函数是带有三个参数的sa_sigaction。