进程间通信-信号-pipe-fifo
编译运行附件中的代码,提交运行结果截图
理解代码,特别是相关系统调用的使用。
一、有名管道FIFO
(一)知识点
1.在有名管道(named pipe或FIFO)提出后,管道(pipe)限制得到了克服。
- FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。因此,通过FIFO不相关的进程也能交换数据。
FIFO类似于放在草坪上的一根塑料水管,任何人都可以将此水管的一段放在耳边,而另一个人通过水管向对方说话。而当没有人使用的时候,水管仍然存在。FIFO可以看做由文件名标志的一根水管。 - 值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
- 它们不支持诸如lseek()等文件定位操作。
2.有名管道的相关操作
与有名管道相关的函数有哪些呢?我们根据“有名”(named)和“管道”(pipe)这两个关键字进行搜索,得到了图中的结果。顾名思义,mkfifo
是我们需要的“创建FIFO”的函数
使用man 3 mkfifo
查看函数的详细信息。mkfifo函数传入值有两个:filename和mode
其中,filename是要创建的管道,mode是管道类型
创建成功后,使用open()、read()和write()函数
- 为读而打开的管道:在open()中设置O_RDONLY;
- 为写而打开的管道:在open()中设置O_WDONLY;
- 非阻塞打开:在open()中设定为O_NONBLOCK
(1)读进程
- 若该管道是阻塞打开,且当前FIFO 内没有数据,则对读进程而言将一直阻塞到有数据写入。
- 若该管道是非阻塞打开,则不论FIFO 内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,则读函数将立刻返回0。
(2)写进程
- 若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。
- 若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败。
3.小结
- 管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。
- FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信:(1)提供一个路径名与之关联;(2)严格遵循先进先出(first in first out) 。
- 管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。
- 要灵活应用管道及FIFO,理解它们的读写规则是关键。
(二)代码
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);
}
以“0777”文件创建模式(即-rwx rwx rwx)创建管道。
- 777 代表
- 该文件拥有者对该文件拥有读写操作的权限
- 该文件拥有者所在组的其他成员对该文件拥有读写操作的权限
- 其他用户组的成员对该文件也拥有读写操作权限
- 666代表
- 该文件拥有者对该文件拥有读写的权限但是没有操作的权限
- 该文件拥有者所在组的其他成员对该文件拥有读写的权限但是没有操作的权限
- 其他用户组的成员对该文件也拥有读写权限但是没有操作的权限
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.管道概述
管道是Linux中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有以下特点:
- 它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。
- 它是一个半双工的通信模式,具有固定的读端和写端。需要双方通信时,需要建立起两个管道。
- 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2.管道系统调用
我们再使用 man 2 pipe
查看函数的详细信息。我们注意到,为pipe函数传的参数为两个文件描述符。
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,这样就构成了一个半双工的通道。
管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符。
-
注意:当一个管道共享多对文件描述符时,若将其中一对读写文件描述符都删除,则该管道就失效。
一般情况下使用管道时,先创建一个管道,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道。为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在下图中将父进程的写端fd[1]和子进程的读端fd[0]关闭。此时,父子进程之间就建立起了一条“子进程写入父进程读取”的通道。
同样,也可以关闭父进程的fd[0]和子进程的fd[1],这样就可以建立一条“父进程写入子进程读取”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1],这时,只需要关闭相应端口就可以建立其各子进程之间的通道。
3.标准流管道
与Linux的文件操作中有基于文件流的标准I/O操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行一定操作的可执行文件,例如,用户执行“ls -l”或者自己编写的程序“./pipe”等。由于这一类操作很常用,因此标准流管道就将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步。
-
创建一个管道
-
fork()一个子进程
-
在父子进程中关闭不需要的文件描述符
-
执行exec函数族调用
-
执行函数中所指定的命令
![image-20221113223635990]使用
man 3 popen
查看函数的详细使用方法,其中, -
command:指向的是一个以null 结束符结束的字符串,这个字符串包含一个shell命令,由shell来执行;
-
type:“r”:文件指针连接到command 的标准输出,即该命令的结果产生输出
“w”:文件指针连接到command 的标准输入,即该命令的结果产生输入
与之相对应,关闭用popen()创建的流管道必须使用函数pclose()来关闭该管道流。该函数关闭标准I/O流,并等待命令执行结束。
4.小结
管道的主要局限性正体现在它的特点上:
- 只支持单向数据流;
- 只能用于具有亲缘关系的进程之间;
- 没有名字,不方便操作;
- 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小)。
(二)代码
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");
}
2.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 );
}
}
}
5.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 );
}
6.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 );
}
7.testtty.c
#include <unistd.h>
int main()
{
char *buf = "abcde\n";
write(0, buf, 6);
}
8.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
(一)
(二)代码
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);
}
- 输入信号(其所对应的数据为 s),打印Called with signal s,等待 4* s 秒,打印done handling signal 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;
}
- 每隔2秒打印Two seconds passed。
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");
}
- 重复间隔2秒打印5次 hello,若输入 ^C,打印 OUCH!
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");
}
}
- 不断间隔1秒打印 haha,且输入 ^C 无法退出。
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");
}
- 当输入正常数据时,将其打印出;
- 当输入 ^C 时,打印 Received signal 2 .. waiting ,等待2秒后打印Leaving inthandler;
- 当输入 ^\ 时,打印 Received signal 3 .. waiting ,等待3秒后打印Leaving quithandler。