进程间通信-信号-pipe-fifo

一、FIFO有名管道

(一)知识点归纳

  • FIFO也称为有名管道,FIFO不同于管道之处在于它提供一个路径名与之关联。有名管道也被称为FIFO文件,是一种特殊的文件。由于linux所有的事物都可以被视为文件,所以对命名管道的使用也就变得与文件操作非常统一。其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

  • 一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的 I/O 系统调用了(如read()、write() 和 close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。

(二)使用方法

  • 通过命令创建有名管道:mkfifo name

  • 通过函数创建有名管道

使用man 3 mkfifo查看函数详情:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

pathname:管道名称的路径
mode:文件的权限
返回值:成功返回0,失败返回-1。

(三)读管道

  • 管道中有数据:read 返回实际读到的字节数

  • 管道中无数据:

    • 管道写端被全部关闭,read 返回 0(相当与读到文件末尾)。
    • 写端没有全部被关闭:read 阻塞等待

(四)写管道

  • 管道读端被全部关闭,进程异常终止(收到一个 SIGPIPE 信号)

  • 管道读端没有全部关闭:

    • 管道已经满了,write 会阻塞
    • 管道没有满,write 将数据写入,并返回实际写入的字节数

(五)代码

  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.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);
}
  1. 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);
}

(六)运行结果

二、管道(PIPE)

(一)知识归纳

  • 不同进程间的通信:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同,而 pipe就是提供这份公共资源的形式的一种。其本质是一个伪文件(实为内核缓冲区),由两个文件描述符引用,一个表示读端,一个表示写端。

  • 局限性

  1. 数据自己读不能自己写。

  2. 数据一旦被读走,便不在管道中存在,不可反复读取。

  3. 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

  4. 只能在有公共祖先的进程间使用管道。

常见的通信方式有,单工通信、半双工通信、全双工通信。

(二)创建管道

  • 通过指令 man -k pipe | grep create 寻找所需函数

#include <unistd.h>
int pipe (int fd[2]);
                         //返回:成功返回0,出错返回-1 
  • 创建过程

(1)父进程创建管道,得到两个件描述符指向管道的两端

(2)父进程fork出子进程,子进程也有两个文件描述符指向同管道。

(3)父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

(三)标准流管道

标准流管道将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步。

  • 创建一个管道
  • fork()一个子进程
  • 在父子进程中关闭不需要的文件描述符
  • 执行exec函数族调用
  • 执行函数中所指定的命令

(四)代码

  1. listargs.c
#include<stdio.h>

main( int ac, char *av[] )
{
	int	i;

	printf("Number of args: %d, Args are:\n", ac);
	for(i=0;i<ac;i++)
		printf("args[%d] %s\n", i, av[i]);

	fprintf(stderr,"This message is sent to stderr.\n");
}

  1. pipe.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

#define	oops(m,x)	{ perror(m); exit(x); }

int main(int ac, char **av)
{
	int	thepipe[2],
		newfd,
		pid;

	if ( ac != 3 ){
		fprintf(stderr, "usage: pipe cmd1 cmd2\n");
		exit(1);
	}
	if ( pipe( thepipe ) == -1 )
		oops("Cannot get a pipe", 1);

	if ( (pid = fork()) == -1 )
		oops("Cannot fork", 2);

	if ( pid > 0 ){
		close(thepipe[1]);

		if ( dup2(thepipe[0], 0) == -1 )
			oops("could not redirect stdin",3);

		close(thepipe[0]);
		execlp( av[2], av[2], NULL);
		oops(av[2], 4);
	}

	close(thepipe[0]);

	if ( dup2(thepipe[1], 1) == -1 )
		oops("could not redirect stdout", 4);

	close(thepipe[1]);
	execlp( av[1], av[1], NULL);
	oops(av[1], 5);
}

3.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;
		}
	}
}

4.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 );
			}
	}
}

  1. 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 );
}

  1. stdinredir2.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>

//#define	CLOSE_DUP
//#define	USE_DUP2

main()
{
	int	fd ;
	int	newfd;
	char	line[100];

	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );

	fd = open("data", O_RDONLY);
#ifdef CLOSE_DUP
	close(0);
	newfd = dup(fd);
#else
	newfd = dup2(fd,0);
#endif
	if ( newfd != 0 ){
		fprintf(stderr,"Could not duplicate fd to 0\n");
		exit(1);
	}
	close(fd);

	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
}

  1. 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;
}

  1. testtty.c
#include <unistd.h>

int main()
{
	char *buf = "abcde\n";
	write(0, buf, 6);
}

三、signal

(一)信号基本概念

信号是一条小的消息,由内核或者其它进程生成并发送至目标进程,目标进程可以根据该信号来做出响应。信号可以由进程或者内核发出,例如:

  • 用户在Bash界面通过键盘对正在执行的进程输入Ctrl+C、Ctrl+\等信号命令,或者执行kill命令发送信号。

  • 进程执行出错,例如访问了一个非法的地址、除0运算,或者硬件发生故障,就会由内核向进程发送一个信号。

  • 进程执行kill命令向目标进程发送信号。

(二)信号发送步骤

  • 发送信号:内核通过更新目标进程上下文的某个状态,传递一个信号给目标进程。

  • 接收信号:目标进程会被内核强制以某种方式对信号的发送做出反应,它就会接收到信号。如果程序没有针对这种信号指定其处理方式,就会采用默认的处理策略,例如中止进程、忽略。

(三)信号种类

每种信号对应于一种系统事件,Linux提供以下几种基本的信号种类:

[root@bogon ~]# kill -l

  1. SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
  2. SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
  3. SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
  4. SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  5. SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
  6. SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
  7. SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
  8. SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
  9. SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  10. SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  11. SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
  12. SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
  13. SIGRTMAX-1 64) SIGRTMAX
  • 每个信号类型都有一个预定义的默认行为:
  1. 进程终止
  2. 进程终止并且将进程上下文dump到硬盘
  3. 进程挂起直到被SIGCONT信号唤醒
  4. 进程接收该信号,不进行任何处理。

(四)代码

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");
}

posted @ 2022-11-10 18:55  20201325my  阅读(74)  评论(0编辑  收藏  举报