《linux进程通讯-纯文本文件》
时间:2010-10-19 来源:ckhitler
一)概述:
1)纯文本文件是一种原始但却高效的进程间通信方式,当两个不同步执行的进程必须要进行通信时,文件或许是进行IPC的唯一选择.
2)一般来讲通过纯文本文件在多个进程之间进行过渡,传输数据,而gcc编译程序就是一个例子,它会生成中间文件,最后再将其删除.
3)当两个进程使用文件进行通信时,无法保证当一个进程在读的时候,另一个进程没有去写,下面的例子用于说明这个问题.
二)文本文件的IPC和lockf函数
源程序1如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL)
error_out("child:fopen"); char buf[32];
fread(buf, sizeof(buf), 1, fp);
printf("child read: %s\n", buf);
fclose(fp);
}
void parent(void)
{
FILE *fp = fopen(filename, "w");
if (fp == NULL)
error_out("parent:fopen"); fprintf(fp, "Hello world");
fclose(fp);
}
int main (int argc, char *argvp[])
{
pid_t pid = fork();
if (pid == 0){
child();
}
else{
parent();
int status = 0;
int r = wait(&status);
if (r == -1)
error_out("parent:wait"); printf("child status=%d\n", WEXITSTATUS(status));
unlink(filename);
}
exit(0);
} gcc file-ipc-naive.c -o file-ipc-naive 当运行时返回下面的错误信息
./file-ipc-naive
child:fopen: No such file or directory
child status=1 我们来分析一下上面的程序,程序运行后即执行了fork,此时派生了子进程,执行了child();而父进程执行了parent();
子进程通过fopen(filename, "r")试图打开messagebuf.dat文件,而此时如果父进程没有执行到fopen(filename, "w"),这时程序就会报上面的错误.
而如果我们通过strace运行file-ipc-navie这个程序,返回的结果也许会不同,如下:
strace -o strace.out -f ./file-ipc-naive
child read: Hello world;
child status=0
原因在于用strace监视程序运行时,有充足的时间让程序可以输出正确的结果,但不是每次都能得到正确的输出. 为解决这个问题,我们可以用lockf函数对文件进行锁定控制.
一个进程打开文件,并锁住该文件,另一个进程读取这部份数据,就进入锁等待.例如下面的程序: 源程序2:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r+");
if (fp == NULL)
error_out("child:fopen"); int r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); char buf[32];
fread(buf,sizeof(buf), 1, fp);
if (ferror(fp))
error_out("fread"); printf("child read '%s'\n", buf);
} void parent(FILE *fp)
{
fprintf(fp, "%#x", getpid());
fflush(fp); int r = lockf(fileno(fp), F_ULOCK, 0);
if (r == -1)
error_out("lockf:F_ULOCK");
fclose(fp);
} int main(int argc, char *argv[])
{
int r;
int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666); FILE *fp = fdopen(fd, "r+");
if (fp == NULL)
error_out("parent:fopen"); r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); pid_t pid = fork(); if (pid == 0){
child();
exit(0);
}
else{
int status = 0;
parent(fp);
wait(&status); printf("child status=%d\n", WEXITSTATUS(status));
}
unlink (filename);
exit(0);
} gcc file-ipc-better.c -o file-ipc-better ./file-ipc-better
child read '0x4fa9'
child status=0 改进后的程序在创建子进程前创建了文件,并且利用lockf锁定了文件,当子进程打开文件后,发现文件已经在锁定状态,这时要等父进程写入完成,解锁后它才能处理.
父进程通过lockf对文件进行解锁,父进程通过这种办法可以保证文件内容在子进程读取之前的有效性,随后子进程对文件进行锁定,并完成读写.
我们通过lockf对父子进程通讯写入进行保护.
三)强制性加锁的实现 1)对文件加锁有两种方式:劝告性锁和强制性锁.
2)劝告性锁工作时,每个进程都要对文件进行读或写之前调用lockf对文件加锁,如果一个进程忘记调用lockf,那么锁协议将会被忽视
3)强制性锁工作时,它将使任何一个想要访问已被加锁的文件的进程都堵塞在读或写队列上.加锁的过程是由内核强制启动的,所以不用担心不同步的进程忽视劝告性锁.
4)程序2就是劝告性锁的典型例子,而下面的例子会演示强制性锁的使用.
mount -o remount,rw,mand /dev/sdb1 /mnt/
cd /mnt/
chmod g-x messagebuf.dat
chmod g+s messagebuf.dat 修改程序2的源代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r+");
if (fp == NULL)
error_out("child:fopen"); /*int r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf");*/ char buf[32];
fread(buf,sizeof(buf), 1, fp);
if (ferror(fp))
error_out("fread"); printf("child read '%s'\n", buf);
} void parent(FILE *fp)
{
fprintf(fp, "%#x", getpid());
fflush(fp); int r = lockf(fileno(fp), F_ULOCK, 0);
if (r == -1)
error_out("lockf:F_ULOCK");
fclose(fp);
} int main(int argc, char *argv[])
{
int r;
int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666); FILE *fp = fdopen(fd, "r+");
if (fp == NULL)
error_out("parent:fopen"); r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); pid_t pid = fork(); if (pid == 0){
child();
exit(0);
}
else{
int status = 0;
parent(fp);
wait(&status); printf("child status=%d\n", WEXITSTATUS(status));
}
exit(0);
} gcc file-ipc-better.c -o file-ipc-better
./file-ipc-better
child read '0x9a0'
child status=0 我们注释掉了子进程的lockf函数的过程,这时如果没有强制锁,子进程的读操作,将会忽视父进程的lockf,而我们加了强制锁,子进程的读操作被阻塞了.
最终的结果同第二个程序一样,但我们却使用了不同的锁机制. 最后使用文件进行传递媒介就意味着你可能遇到媒介中存在的潜在不安全因素.
1)纯文本文件是一种原始但却高效的进程间通信方式,当两个不同步执行的进程必须要进行通信时,文件或许是进行IPC的唯一选择.
2)一般来讲通过纯文本文件在多个进程之间进行过渡,传输数据,而gcc编译程序就是一个例子,它会生成中间文件,最后再将其删除.
3)当两个进程使用文件进行通信时,无法保证当一个进程在读的时候,另一个进程没有去写,下面的例子用于说明这个问题.
二)文本文件的IPC和lockf函数
源程序1如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL)
error_out("child:fopen"); char buf[32];
fread(buf, sizeof(buf), 1, fp);
printf("child read: %s\n", buf);
fclose(fp);
}
void parent(void)
{
FILE *fp = fopen(filename, "w");
if (fp == NULL)
error_out("parent:fopen"); fprintf(fp, "Hello world");
fclose(fp);
}
int main (int argc, char *argvp[])
{
pid_t pid = fork();
if (pid == 0){
child();
}
else{
parent();
int status = 0;
int r = wait(&status);
if (r == -1)
error_out("parent:wait"); printf("child status=%d\n", WEXITSTATUS(status));
unlink(filename);
}
exit(0);
} gcc file-ipc-naive.c -o file-ipc-naive 当运行时返回下面的错误信息
./file-ipc-naive
child:fopen: No such file or directory
child status=1 我们来分析一下上面的程序,程序运行后即执行了fork,此时派生了子进程,执行了child();而父进程执行了parent();
子进程通过fopen(filename, "r")试图打开messagebuf.dat文件,而此时如果父进程没有执行到fopen(filename, "w"),这时程序就会报上面的错误.
而如果我们通过strace运行file-ipc-navie这个程序,返回的结果也许会不同,如下:
strace -o strace.out -f ./file-ipc-naive
child read: Hello world;
child status=0
原因在于用strace监视程序运行时,有充足的时间让程序可以输出正确的结果,但不是每次都能得到正确的输出. 为解决这个问题,我们可以用lockf函数对文件进行锁定控制.
一个进程打开文件,并锁住该文件,另一个进程读取这部份数据,就进入锁等待.例如下面的程序: 源程序2:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r+");
if (fp == NULL)
error_out("child:fopen"); int r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); char buf[32];
fread(buf,sizeof(buf), 1, fp);
if (ferror(fp))
error_out("fread"); printf("child read '%s'\n", buf);
} void parent(FILE *fp)
{
fprintf(fp, "%#x", getpid());
fflush(fp); int r = lockf(fileno(fp), F_ULOCK, 0);
if (r == -1)
error_out("lockf:F_ULOCK");
fclose(fp);
} int main(int argc, char *argv[])
{
int r;
int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666); FILE *fp = fdopen(fd, "r+");
if (fp == NULL)
error_out("parent:fopen"); r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); pid_t pid = fork(); if (pid == 0){
child();
exit(0);
}
else{
int status = 0;
parent(fp);
wait(&status); printf("child status=%d\n", WEXITSTATUS(status));
}
unlink (filename);
exit(0);
} gcc file-ipc-better.c -o file-ipc-better ./file-ipc-better
child read '0x4fa9'
child status=0 改进后的程序在创建子进程前创建了文件,并且利用lockf锁定了文件,当子进程打开文件后,发现文件已经在锁定状态,这时要等父进程写入完成,解锁后它才能处理.
父进程通过lockf对文件进行解锁,父进程通过这种办法可以保证文件内容在子进程读取之前的有效性,随后子进程对文件进行锁定,并完成读写.
我们通过lockf对父子进程通讯写入进行保护.
三)强制性加锁的实现 1)对文件加锁有两种方式:劝告性锁和强制性锁.
2)劝告性锁工作时,每个进程都要对文件进行读或写之前调用lockf对文件加锁,如果一个进程忘记调用lockf,那么锁协议将会被忽视
3)强制性锁工作时,它将使任何一个想要访问已被加锁的文件的进程都堵塞在读或写队列上.加锁的过程是由内核强制启动的,所以不用担心不同步的进程忽视劝告性锁.
4)程序2就是劝告性锁的典型例子,而下面的例子会演示强制性锁的使用.
mount -o remount,rw,mand /dev/sdb1 /mnt/
cd /mnt/
chmod g-x messagebuf.dat
chmod g+s messagebuf.dat 修改程序2的源代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h> const char *filename = "messagebuf.dat"; void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
} void child(void)
{
FILE *fp = fopen(filename, "r+");
if (fp == NULL)
error_out("child:fopen"); /*int r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf");*/ char buf[32];
fread(buf,sizeof(buf), 1, fp);
if (ferror(fp))
error_out("fread"); printf("child read '%s'\n", buf);
} void parent(FILE *fp)
{
fprintf(fp, "%#x", getpid());
fflush(fp); int r = lockf(fileno(fp), F_ULOCK, 0);
if (r == -1)
error_out("lockf:F_ULOCK");
fclose(fp);
} int main(int argc, char *argv[])
{
int r;
int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666); FILE *fp = fdopen(fd, "r+");
if (fp == NULL)
error_out("parent:fopen"); r = lockf(fileno(fp), F_LOCK, 0);
if (r == -1)
error_out("parent:lockf"); pid_t pid = fork(); if (pid == 0){
child();
exit(0);
}
else{
int status = 0;
parent(fp);
wait(&status); printf("child status=%d\n", WEXITSTATUS(status));
}
exit(0);
} gcc file-ipc-better.c -o file-ipc-better
./file-ipc-better
child read '0x9a0'
child status=0 我们注释掉了子进程的lockf函数的过程,这时如果没有强制锁,子进程的读操作,将会忽视父进程的lockf,而我们加了强制锁,子进程的读操作被阻塞了.
最终的结果同第二个程序一样,但我们却使用了不同的锁机制. 最后使用文件进行传递媒介就意味着你可能遇到媒介中存在的潜在不安全因素.
相关阅读 更多 +