进程间通信-信号-pipe-fifo
相关概念
FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读的方式打开该文件,而另一个进程以写的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接。
-
它可以使不相关的两个进程实现彼此通信;
-
该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便;
-
FIFO严格地遵循先进先出规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则是把数据添加到末尾,它们不支持如 lseek() 等文件操作。
代码运行
1.testmf.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("/tmp/myfifo", 0777);
if (res == 0) {
printf("FIFO created \n");
}
exit(EXIT_SUCCESS);
}
2.consumer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes = 0;
memset(buffer, 0, sizeof(buffer));
printf("Process %d opeining FIFO O_RDONLY \n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
do {
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes += res;
} while (res > 0);
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read\n", getpid(), bytes);
exit(EXIT_SUCCESS);
}
3.producer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) == -1) {
res = mkfifo(FIFO_NAME, 0777);
if (res != 0) {
fprintf(stderr, "Could not create fifo %s \n",
FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
while (bytes < TEN_MEG) {
res = write(pipe_fd, buffer, BUFFER_SIZE);
if (res == -1) {
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
bytes += res;
}
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finish\n", getpid());
exit(EXIT_SUCCESS);
}
二、无名管道(pipe):
无名管道的特点: 1、管道是半双工的,数据只能向一个方向流动,具有固定的读端和写端;需要双方通信时,需要建立起两个管道; 2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程); 3、单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是单独构成一种文件系统,并且只存在与内存中。 4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道读写注意点:
-
只有在管道的读端存在时,向管道写入数据才有意义。否则向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为Broken pipe错误);
-
向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞;
-
父子进程在运行时,它们的先后顺序并不能保证
代码运行
1.pipe.c
2.pipedemo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int len, i, apipe[2];
char buf[BUFSIZ];
if ( pipe ( apipe ) == -1 ){
perror("could not make pipe");
exit(1);
}
printf("Got a pipe! It is file descriptors: { %d %d }\n",
apipe[0], apipe[1]);
while ( fgets(buf, BUFSIZ, stdin) ){
len = strlen( buf );
if ( write( apipe[1], buf, len) != len ){
perror("writing to pipe");
break;
}
for ( i = 0 ; i<len ; i++ )
buf[i] = 'X' ;
len = read( apipe[0], buf, BUFSIZ ) ;
if ( len == -1 ){
perror("reading from pipe");
break;
}
if ( write( 1 , buf, len ) != len ){
perror("writing to stdout");
break;
}
}
}
3.pipedemo2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PAR_MESS "testing..\n"
#define oops(m,x) { perror(m); exit(x); }
main()
{
int pipefd[2];
int len;
char buf[BUFSIZ];
int read_len;
if ( pipe( pipefd ) == -1 )
oops("cannot get a pipe", 1);
switch( fork() ){
case -1:
oops("cannot fork", 2);
case 0:
len = strlen(CHILD_MESS);
while ( 1 ){
if (write( pipefd[1], CHILD_MESS, len) != len )
oops("write", 3);
sleep(5);
}
default:
len = strlen( PAR_MESS );
while ( 1 ){
if ( write( pipefd[1], PAR_MESS, len)!=len )
oops("write", 4);
sleep(1);
read_len = read( pipefd[0], buf, BUFSIZ );
if ( read_len <= 0 )
break;
write( 1 , buf, read_len );
}
}
}
4.stdinredir1.c
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd ;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
close(0);
fd = open("/etc/passwd", O_RDONLY);
if ( fd != 0 ){
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
5.stdinredir2.c
6.testtty.c
#include <unistd.h>
int main()
{
char *buf = "abcde\n";
write(0, buf, 6);
}
7.whotofile.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int pid ;
int fd;
printf("About to run who into a file\n");
if( (pid = fork() ) == -1 ){
perror("fork"); exit(1);
}
if ( pid == 0 ){
close(1); /* close, */
fd = creat( "userlist", 0644 ); /* then open */
execlp( "who", "who", NULL ); /* and run */
perror("execlp");
exit(1);
}
if ( pid != 0 ){
wait(NULL);
printf("Done running who. results in userlist\n");
}
return 0;
}
三、SIGNAL
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
代码
1.sigactdemo.c
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#define INPUTLEN 100
void inthandler();
int main()
{
struct sigaction newhandler;
sigset_t blocked;
char x[INPUTLEN];
newhandler.sa_handler = inthandler;
newhandler.sa_flags = SA_RESTART|SA_NODEFER
|SA_RESETHAND;
sigemptyset(&blocked);
sigaddset(&blocked, SIGQUIT);
newhandler.sa_mask = blocked;
if (sigaction(SIGINT, &newhandler, NULL) == -1)
perror("sigaction");
else
while (1) {
fgets(x, INPUTLEN, stdin);
printf("input: %s", x);
}
return 0;
}
void inthandler(int s)
{
printf("Called with signal %d\n", s);
sleep(s * 4);
printf("done handling signal %d\n", s);
}
2.sigactdemo2.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void sig_alrm( int signo )
{
/*do nothing*/
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset( &newact.sa_mask );
newact.sa_flags = 0;
sigaction( SIGALRM, &newact, &oldact );
alarm( nsecs );
pause();
unslept = alarm ( 0 );
sigaction( SIGALRM, &oldact, NULL );
return unslept;
}
int main( void )
{
while( 1 )
{
mysleep( 2 );
printf( "Two seconds passed\n" );
}
return 0;
}
3.sigdemo1.c
#include<stdio.h>
#include<signal.h>
void f(int);
int main()
{
int i;
signal( SIGINT, f );
for(i=0; i<5; i++ ){
printf("hello\n");
sleep(2);
}
return 0;
}
void f(int signum)
{
printf("OUCH!\n");
}
4.sigdemo2.c
#include<stdio.h>
#include<signal.h>
main()
{
signal( SIGINT, SIG_IGN );
printf("you can't stop me!\n");
while( 1 )
{
sleep(1);
printf("haha\n");
}
}
5.sigdemo3.c
#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
#define INPUTLEN 100
int main(int argc, char *argv[])
{
void inthandler(int);
void quithandler(int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);//^C
signal(SIGQUIT, quithandler);//^\
do {
printf("\nType a message\n");
nchars = read(0, input, (INPUTLEN - 1));
if (nchars == -1)
perror("read returned an error");
else {
input[nchars] = '\0';
printf("You typed: %s", input);
}
}
while (strncmp(input, "quit", 4) != 0);
return 0;
}
void inthandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(2);
printf(" Leaving inthandler \n");
}
void quithandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(3);
printf(" Leaving quithandler \n");
}