第 10 章linux进程间通信 1. 进程间通信概述

来源:互联网 发布:校园网网络拓扑图 编辑:程序博客网 时间:2024/05/21 07:47

进程间通信有如下一些目的:

 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。

 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)

 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

进程控制:有些进程希望完全控制另一个进程的执行(Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

linux进程间通信(IPC)由以下几部分发展而来:

早期UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和POSIX进程间通信。

UNIX进程间通信方式包括:管道、FIFO、信号。

System V进程间通信方式包括:System V消息队列、System V信号灯、System V共享内存。

POSIX进程间通信包括:posix消息队列、posix信号灯、posix共享内存。

现在linux使用的进程间通信方式:

(1)管道(pipe)名管道(FIFO)

(2)信号(signal)

(3)消息队列

(4)共享内存

(5)信号量

(6)套接字(socket)

2. 管道通信

普通的Linux shell都允许重定向,而重定向使用的就是管道。例如:

ps | grep vsftpd

管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。管道主要用于不同进程间通信。

2.1 管道创建与关闭

创建一个简单的管道,可以使用系统调用pipe()

它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。

系统调用:pipe();

原型:int pipe( int fd[2]);

返回值:如果系统调用成功,返回0。如果系统调用失败返回- 1

errno = EMFILE (没有空闲的文件描述符)

EMFILE (系统文件表已满)

EFAULT (fd数组无效)

注意:fd[0] 用于读取管道,fd[1] 用于写入管道。



cat pipe.c

#include <unistd.h>

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>


int main()

{

int pipe_fd[2];

if(pipe(pipe_fd)<0)

{

printf("pipe create error\n");

return -1;

}

else

printf("pipe create success\n");

close(pipe_fd[0]);

close(pipe_fd[1]);

}

2.2 管道读写

管道主要用于不同进程间通信。实际上,通常先创建一个管道,再通过fork函数创建一个子进程。

子进程写入和父进程读的命名管道:

2.3 管道读写注意事项

可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述符。

必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。

管道实例见:pipe_rw.c

cat pipe_rw.c

#include <unistd.h>

#include <sys/types.h>

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

int pipe_fd[2];

pid_t pid;

char buf_r[100];

char* p_wbuf;

int r_num;

memset(buf_r,0,sizeof(buf_r));

if(pipe(pipe_fd)<0)

{

printf("pipe create error\n");

return -1;

}

if((pid=fork())==0)

{

printf("\n");

close(pipe_fd[1]);

sleep(2);

if((r_num=read(pipe_fd[0],buf_r,100))>0){

printf( "%d numbers read from the pipe is %s\n",r_num,buf_r);

}

close(pipe_fd[0]);

exit(0);

}

else if(pid>0)

{

close(pipe_fd[0]);

if(write(pipe_fd[1],"Hello",5)!=-1)

printf("parent write1 success!\n");

if(write(pipe_fd[1]," Pipe",5)!=-1)

printf("parent write2 success!\n");

close(pipe_fd[1]);

sleep(3);

waitpid(pid,NULL,0);

exit(0);

}

}

2.4 标准流管道

linux中文件操作有文件流的标准I/O一样,管道的操作也支持基于文件流的模式。接口函数如下

库函数:popen();

原型: FILE *popen ( char *command, char *type);

返回值:如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回NULL

管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在Linux系统下,管道将会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。

使用popen()创建的管道必须使用pclose()关闭。

其实,popen/pclose和标准文件输入/输出流中的fopen() / fclose()十分相似。

库函数: pclose();

原型: int pclose( FILE *stream);

返回值: 返回系统调用wait4()的状态。

如果stream无效,或者系统调用wait4()失败,则返回 -1

注意此库函数等待管道进程运行结束,然后关闭文件流。

库函数pclose()在使用popen()创建的进程上执行wait4()函数。当它返回时,它将破坏管道和文件系统

cat popen.c

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <fcntl.h>

#define BUFSIZE 1000

int main()

{

FILE *fp;

char *cmd = "ps -ef";

char buf[BUFSIZE];

buf[BUFSIZE] = '\0';

if((fp=popen(cmd,"r"))==NULL)

perror("popen");

while((fgets(buf,BUFSIZE,fp))!=NULL)

printf("%s",buf);

pclose(fp);

exit(0);

}

2.5 命名管道(FIFO

2.5.1 基本概念

命名管道和一般的管道基本相同,但也有一些显著的不同:

命名管道是在文件系统中作为一个特殊的设备文件而存在的。

不同祖先的进程之间可以通过管道共享数据。

当共享管道的进程执行完所有的I / O操作以后,命名管道将继续保存在文件系统中以便以后使用。

管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。

2.5.2 命名管道创建与操作

名管道创建

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char * pathname,mode_t mode) ;

返回:若成功则为0,若出错则为- 1

一旦已经用mkfifo创建了一个FIFO,就可用open打开它。确实,一般的文件I / O(closereadwriteunlink等)都可用于FIFO

当打开一个FIFO,非阻塞标志(O_NONBLOCK)产生下列影响:

(1) 在一般情况中(没有说明O_NONBLOCK,只读打开要阻塞到某个其他进程为写打开此FIFO。类似,为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。

(2) 如果指定了O_NONBLOCK,则只读打开立即返回。但是,如果没有进程已经为读而打开一个FIFO,那么只写打开将出错返回,errnoENXIO

类似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。

FIFO相关出错信息:

 EACCES (无存取权限)

 EEXIST (指定文件不存在)

 ENAMETOOLONG (路径名太长)

 ENOENT (包含的目录不存在)

 ENOSPC (文件系统剩余空间不足)

 ENOTDIR (文件路径无效)

 EROFS (指定的文件存在于只读文件系统中)

实例见:fifo_write.c 、 fifo_read.c

cat fifo_read.c

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define FIFO "/tmp/myfifo"

main(int argc,char** argv)

{

char buf_r[100];

int fd;

int nread;

if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

printf("cannot create fifoserver\n");

printf("Preparing for reading bytes...\n");

memset(buf_r,0,sizeof(buf_r));

fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);

if(fd==-1)

{

perror("open");

exit(1);

}

while(1)

{

memset(buf_r,0,sizeof(buf_r));



if((nread=read(fd,buf_r,100))==-1){

if(errno==EAGAIN)

printf("no data yet\n");

}

printf("read %s from FIFO\n",buf_r);

sleep(1);

}

pause();

unlink(FIFO);

}

cat fifo_write.c

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define FIFO_SERVER "/tmp/myfifo"

main(int argc,char** argv)

{

int fd;

char w_buf[100];

int nwrite;



if(fd==-1)

if(errno==ENXIO)

printf("open error; no reading process\n");

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

if(argc==1)

printf("Please send something\n");

strcpy(w_buf,argv[1]);

if((nwrite=write(fd,w_buf,100))==-1)

{

if(errno==EAGAIN)

printf("The FIFO has not been read yet.Please try later\n");

}

else

printf("write %s to the FIFO\n",w_buf);

}

3. 信号

 

3.1 信号概述

信号是软件中断。信号(signal)机制是Unix系统中最为古老的进程之间的通信机制。它用于在一个或多个进程之间传递异步信号。

很多条件可以产生一个信号。

当用户按某些终端键时,产生信号。在终端上按DELETE键通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。(11章将说明此信号可被映

射为终端上的任一字符。)

硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。

例如,对执行一个无效存储访问的进程产生一个SIGSEGV进程用kill( 2)函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

用户可用kill( 1)命令将信号发送给其他进程。此程序是kill函数的界面。常用此命令终止一个失控的后台进程。

当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬件产生条件(如被0除),而是软件条件。例如SIGURG (在网络连接上传来非规定波特率的数据)SIGPIPE (在管道的读进程已终止后一个进程写此管道),以及SIGALRM(进程所设置

的闹钟时间已经超时)

内核为进程生产信号,来响应不同的事件,这些事件就是信号源。主要的信号源如下:

异常:进程运行过程中出现异常;

其它进程:一个进程可以向另一个或一组进程发送信号;

终端中断:Ctrl-C,Ctrl-\;

作业控制:前台、后台进程的管理;

分配额:CPU超时或文件大小突破限制;

通知:通知进程某事件发生,I/O就绪等;

报警:计时器到期。

Linux 中的信号:

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE

9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD

18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN

22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

26) SIGVTALRM 27) SIGPROF 28) SIGWINCH

29) SIGIO 30) SIGPWR

下面是几个常见的信号。

SIGHUP :从终端上发出的结束信号;

SIGINT :来自键盘的中断信号(Ctrl-C;

SIGQUIT:来自键盘的退出信号(Ctrl-\;

SIGFPE :浮点异常信号(例如浮点运算溢出);

SIGKILL:该信号结束接收信号的进程;

SIGALRM:进程的定时器到期时,发送该信号;

SIGTERMkill 命令发出的信号;

SIGCHLD:标识子进程停止或结束的信号;

SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号

…………

可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。

(1) 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILLSIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0,则进程的行为是未定义的。

(2) 捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。

(3) 执行系统默认动作。对大多数信号的系统默认动作是终止该进程。

每一个信号都有一个缺省动作,它是当进程没有给这个信号指定处理程序时,内核对信号的

处理。有5种缺省的动作:

异常终止(abort):在进程的当前目录下,把进程的地址空间内容、寄存器内容保存到一

个叫做core的文件中,而后终止进程。

退出(exit):不产生core文件,直接终止进程。

忽略(ignore):忽略该信号。

停止(stop):挂起该进程。

继续(continue):如果进程被挂起,则恢复进程的运行。否则,忽略信号。

3.2 信号发送与捕捉

3.2.1 kill()raise()

kill()不仅可以中止进程,也可以向进程发送其他信号。

kill函数不同的是,raise()函数运行向进程自身发送信号。

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int signo) ;

int raise(int signo) ;

两个函数返回:若成功则为0,若出错则为-1

killpid参数有四种不同的情况:

 pid>0 将信号发送给进程IDpid的进程。

 pid == 0 将信号发送给其进程组I D等于发送进程的进程组ID,而且发送进程有许可权

向其发送信号的所有进程。

 pid < 0 将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信

号的所有进程。如上所述一样,“所有进程”并不包括系统进程集中的进程。

pid ==-1 POSIX.1未定义此种情况。

kill()实例见:kill.c

cat kill.c

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/wait.h>



int main()

{

pid_t pid;

int ret;

if((pid=fork())<0){

perror("fork");

exit(1);

}

if(pid == 0){

raise(SIGSTOP);

exit(0);

}

else{

printf("pid=%d\n",pid);

if((waitpid(pid,NULL,WNOHANG))==0){

if((ret=kill(pid,SIGKILL))==0)

printf("kill %d\n",pid);

else{

perror("kill");

}

}

}

}

3.2.2 alarmpause函数

使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。

#include <unistd.h>

unsigned int alarm(unsigned int seconds) ;

返回:0或以前设置的闹钟时间的余留秒数参数seconds的值是秒数,经过了指定的seconds秒后会产生信号SIGALRM

每个进程只能有一个闹钟时间。如果在调用alarm,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换。

如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。

pause函数使调用进程挂起直至捕捉到一个信号。

#include <unistd.h>

int pause(void);

返回:-1,errno设置为EINTR只有执行了一个信号处理程序并从其返回时,pause才返回。

实例见:alarm.c

cat alarm.c

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

int ret;

ret=alarm(5);

pause();

printf("I have been waken up.\n",ret);

}

3.3 信号的处理

当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。

3.3.1 signal()

#include <signal.h>

void (*signal (int signo, void (*func)(int)))(int)

返回:成功则为以前的信号处理配置,若出错则为SIG_ERR

func的值是: (a)常数SIG_IGN,(b)常数

SIG_DFL,(c)当接到此信号后要调用的函数的地址。如果指定SIG_IGN ,则向内核表示忽略此信号(有两个信号SIGKILLSIGSTOP不能忽略)。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。

signal函数原型太复杂了,如果使用下面的typedef,则可使其简化。

typedef void sign(int);

sign *signal(int, handler *);

实例见:mysignal.c

cat mysignal.c

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

void my_func(int sign_no)

{

if(sign_no==SIGINT)

printf("I have get SIGINT\n");

else if(sign_no==SIGQUIT)

printf("I have get SIGQUIT\n");

}

int main()

{

printf("Waiting for signal SIGINT or SIGQUIT \n ");

signal(SIGINT, my_func);

signal(SIGQUIT, my_func);

pause();

exit(0);

}

3.3.2 信号集函数组

我们需要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。信号集函数组包含几大模块:创建函数集、登记信号集、检测信号集。

3.3.2.1 创建函数集

#include <signal.h>

int sigemptyset(sigset_t * set) ;

int sigfillset(sigset_t * set) ;

int sigaddset(sigset_t * set,int signo) ;

int sigdelset(sigset_t * set,int signo) ;

四个函数返回:若成功则为0,若出错则为-1

int sigismember(const sigset_t * set, int signo) ;

返回:若真则为1,若假则为0

sigemptyset :初始化信号集合为空。

sigfillset:初始化信号集合为所有信号的集合。

sigaddset:将指定信号添加到现存集中。

sigdelset:从信号集中删除指定信号。

sigismember :查询指定信号是否在信号集合中。

3.3.2.2 登记信号集

登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数判断检测或更改信号屏蔽字,然后使sigaction函数改变进程接受到特定信号之后的行为。

一个进程的信号屏蔽字可以规定当前阻塞而不能递送给该进程的信号集。调用sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。

# include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t * oset) ;

返回:若成功则为0,若出错则为-1

oset是非空指针,进程的当前信号屏蔽字通过oset返回。其次,set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

sigprocmask更改当前信号屏蔽字的方法,how参数设定:

 SIG_BLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。

 SIG_UNBLOCK该该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号。

 SIG_SETMASK该该进程新的信号屏蔽是set指向的值。如果set是个空指针,则不改变该进程的信号屏蔽字, how的值也无意义。

sigaction函数的功能是检查或修改(或两者)与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

#include <signal.h>

int sigaction(int signo, const struct sigaction *act,struct sigaction * oact) ;

返回:若成功则为0,若出错则为- 1

参数signo是要检测或修改具体动作的信号的编号数。

act指针非空,则要修改其动作。如果oact指针非空,则系统返回该信号的原先动作。此函数使用下列结构:

struct sigaction {

void (*sa_handler)(int signo);

sigset_t sa_mask;

int sa_flags;

void (*sa_restore);

} ;

sa_handler是一个函数指针,指定信号关联函数,可以是自定义处理函数,还可以SIG_DFL 或 SIG_IGN

sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被阻塞。

sa_flags中包含许多标志位,是对信号进行处理的各种选项。具体如下:

 SA_NODEFER\SA_NOMASK: 当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动阻塞此信号。

 SA_NOCLDSTOP: 进程忽略子进程产生的任何SIGSTOPSIGTSTPSIGTTINSIGTTOU信号

 SA_RESTART: 可让重启的系统调用重新起作用。

 SA_ONESHOT\SA_RESETHAND: 自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作。

3.3.2.3 检测信号集

检测信号是信号处理的后续步骤,但不是必须的。

sigpending函数运行进程检测“未决”信号(进程不清楚他的存在),并进一步决定对他们做何处理。

sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。

#include <signal.h>

int sigpending(sigset_t * set) ;

返回:若成功则为0,若出错则为-1

信号集实例见:sigaction.c

cat sigaction.c

#include <sys/types.h>

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>



void my_func(int signum)

{

printf("If you want to quit,please try SIGQUIT\n");

}

int main()

{

sigset_t set,pendset;

struct sigaction action1,action2;

if(sigemptyset(&set)<0)

perror("sigemptyset");

if(sigaddset(&set,SIGQUIT)<0)

perror("sigaddset");

if(sigaddset(&set,SIGINT)<0)

perror("sigaddset");

if(sigprocmask(SIG_BLOCK,&set,NULL)<0)

perror("sigprocmask");

else

{

printf("blocked\n");

sleep(5);

}

if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)

perror("sigprocmask");

else

printf("unblock\n");

while(1){

if(sigismember(&set,SIGINT)){

sigemptyset(&action1.sa_mask);

action1.sa_handler=my_func;

sigaction(SIGINT,&action1,NULL);

}else if(sigismember(&set,SIGQUIT)){

sigemptyset(&action2.sa_mask);

action2.sa_handler = SIG_DFL;

sigaction(SIGTERM,&action2,NULL);

}

}

}

4. 共享内存

共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。



共享内存实现分为两个步骤:

一、创建共享内存,使用shmget函数。

二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。

系统调用:shmget() ;

原型:int shmget ( key_t key, int size, int shmflg);返回值:如果成功,返回共享内存段标识符。如果失败,则返回- 1

errno = EINVAL (无效的内存段大小)

EEXIST (内存段已经存在,无法创建)

EIDRM (内存段已经被删除)

ENOENT (内存段不存在)

EACCES (权限不够)

ENOMEM (没有足够的内存来创建内存段)

系统调用:shmat();

原型:int shmat ( int shmid, char *shmaddr, int shmflg);返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回- 1

errno = EINVAL (无效的IPC ID 值或者无效的地址)

ENOMEM (没有足够的内存)

EACCES (存取权限不够)

当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。

系统调用:shmdt();

调用原型:int shmdt ( char *shmaddr);

返回值:如果失败,则返回- 1errno = EINVAL (无效的连接地址)

共享内存实例见: share_memory.c

cat share_memory.c

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>

#include <stdlib.h>



#define BUFSZ 2048

int main()

{

int shmid;

char *shmadd;



if((shmid=shmget(IPC_PRIVATE,BUFSZ,0666))<0)

{

perror("shmget");

exit(1);

}

else

printf("created shared-memory: %d\n",shmid);

system("ipcs -m");



if((shmadd=shmat(shmid,0,0))<(char *)0){

perror("shmat");

exit(1);

}

else

printf("attached shared-memory\n");

system("ipcs -m");



if((shmdt(shmadd))<0){

perror("shmdt");

exit(1);

}

else

printf("deleted shared-memory\n");

system("ipcs -m");



exit(0);

}

5. 消息队列

消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。具有一定的FIFO的特性,但是可实现消息的随即查询。这些消息存在于内核中,由“队列ID”来标识。

消息队列的实现包括创建和打开队列、添加消息、读取消息和控制消息队列这四种操作。

msgget:创建和打开队列,其消息数量受系统限制。

msgsnd:添加消息,将消息添加到消息队列尾部。

msgrcv:读取消息,从消息队列中取走消息。

msgctl:控制消息队列。

nt msgget (key_t key, int flag) key:返回新的或已有队列的ID,IPC_PRIVATE

int msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int flag)

其中:msqid是消息队列的队列ID;

 msgp是消息内容所在的缓冲区;

 msgsz是消息的大小;

 msgflg是标志,IPC_NOWAIT若消息并没有立交发送而调用进程会立即返回。

struct msgbuf

{

long mtype; /* type of message */

char mtext[1]; /* message text */

};

int msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,long msgtyp, int flag)

 msqid是消息队列的引用标识符;

 msgp是接收到的消息将要存放的缓冲区;

 msgsz是消息的大小;

 msgtyp是期望接收的消息类型;

 msgflg是标志

int msgctl (int msqid, int cmd, struct msqid_ds *buf)

msqid是消息队列的引用标识符;

cmd是执行命令;

buf是一个缓冲区。

cmd参数指定对于由msqid规定的队列要执行的命令:

 IPC_STAT 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。

 IPC_SET 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四个字段:msg_perm.uidmsg_perm.gidmsg_perm;modemsg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuidmsg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_qbytes的值。

 IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuidmsg_perm.uid;另一种是具有超级用户特权的进程。

消息队列实例见:msg.c

cat msg.c

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#define BUFSZ 512

struct message{

long msg_type;

char msg_text[BUFSZ];

};


int main()

{

int qid;

key_t key;

int len;

struct message msg;


if((key=ftok(".",'a'))==-1)

{

perror("ftok");

exit(1);

}

if((qid=msgget(key,IPC_CREAT|0666))==-1){

perror("msgget");

exit(1);

}

printf("opened queue %d\n",qid);

puts("Please enter the message to queue:");

if((fgets(msg.msg_text,BUFSZ,stdin))==NULL)

{

puts("no message");

exit(1);

}

msg.msg_type = getpid();

len = strlen(msg.msg_text);

if((msgsnd(qid,&msg,len,0))<0){

perror("message posted");

exit(1);

}

if(msgrcv(qid,&msg,BUFSZ,0,0)<0){

perror("msgrcv");

exit(1);

}

printf("message is:%s\n",(&msg)->msg_text);

if((msgctl(qid,IPC_RMID,NULL))<0){

perror("msgctl");

exit(1);

}

exit(0);

}

linux串口应用开发

 

1. 串口概述

用户常见的数据通信的基本方式有两种:

并行通信;

串行通信;

串行通信是计算机常用的接口,如:RS-232-C接口。该标准规定采用一个DB25芯引脚连接器或DB9芯引脚连接器。

芯片内部常具有UART控制器,其可工作于Interrupt(中断模式)DMA(直接内存访问)模式。

UART的操作主要包括以下几个部分:

数据发送;

数据接收;

产生中断;

产生波特率;

Loopback模式;

红外模式;

自动流控模式;

串口参数的配置主要包括:波特率、数据位、停止位、流控协议。

linux中的串口设备文件存放于/dev目录下,其中串口一,串口二对应设备名依次为“/dev/ttyS0”、“/dev/ttyS1”。在linux下操作串口与操作文件相同。

2. 串口设置

在使用串口之前必须设置相关配置,

包括:波特率、数据位、校验位、停止位

等。串口设置由下面结构体实现:

struct termios{

tcflag_t c_iflag; /*input flags*/

tcflag_t c_oflag; /*output flags*/

tcflag_t c_cflag; /*control flags*/

tcflag_t c_lflag; /*local flags*/

cc_t c_cc[NCCS]; /*control characters*/

};

该结构中c_cflag最为重要,可设置波特率、数据位、校验位、停止位。在设置波特率时需在数字前加上‘B’,B9600B19200。使用其需通过“与”“或”操作方式


输入模式c_iflag成员控制端口接收端的字符输入处理。

串口控制函数

Tcgetattr 取属性(termios结构)

Tcsetattr 设置属性(termios结构)

cfgetispeed 得到输入速度

Cfgetospeed 得到输出速度

Cfsetispeed 设置输入速度

Cfsetospeed 设置输出速度

Tcdrain 等待所有输出都被传输

tcflow 挂起传输或接收

tcflush 刷清未决输入和/或输出

Tcsendbreak BREAK字符

tcgetpgrp 得到前台进程组ID

tcsetpgrp 设置前台进程组ID

2.1串口配置流程

1. 保存原先串口配置使用tcgetattr(fd,&oldtio)函数

struct termios newtio,oldtio;tcgetattr( fd,&oldtio);

2. 激活选项有CLOCALCREAD,用于本地连接和接收使能。

newtio.c_cflag | = CLOCAL | CREAD;

3. 设置波特率,使用函数cfsetispeed 、 cfsetospeed cfsetispeed(&newtio, B115200); cfsetospeed(&newtio, B115200);

4. 设置数据位,需使用掩码设置。

newtio.c_cflag &= ~CSIZE;

newtio.c_cflag |= CS8;

5. 设置奇偶校验位,使用c_cflagc_iflag

设置奇校验:

newtio.c_cflag |= PARENB;

newtio.c_cflag |= PARODD;

newtio.c_iflag |= (INPCK | ISTRIP);

设置偶校验:

newtio.c_iflag |= (INPCK | ISTRIP);

newtio.c_cflag |= PARENB;

newtio.c_cflag &= ~PARODD;

6. 设置停止位,通过激活c_cflag中的CSTOPB实现。若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB

newtio.c_cflag &= ~CSTOPB;

7. 设置最少字符和等待时间,对于接收字符和等待时间没有特别要求时,可设为0

newtio.c_cc[VTIME] = 0;

newtio.c_cc[VMIN] = 0;

8.处理要写入的引用对象tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓存(用户程序已经写,但尚未发送)。

int tcflush(int filedes, int queue)

queue数应当是下列三个常数之一:

TCIFLUSH 刷清输入队列。

TCOFLUSH 刷清输出队列。

TCIOFLUSH 刷清输入、输出队列。

如:tcflush(fd,TCIFLUSH);

9.激活配置。在完成配置后,需激活配置使其生效。使用tsettattr()函数。原型:

int tcgetattr(int filedes, struct termios *termptr);

int tcsetattr(int filedes, int opt, const struct termios * termptr);

tcsetattr的参数opt 使我们可以指定在什么时候新的终端属性才起作用。opt 可以指定为下列常数中的一个:

• TCSANOW 更改立即发生。

• TCSADRAIN 发送了所有输出后更改才发生。若更改输出参数则应使用此选择项。

• TCSAFLUSH 发送了所有输出后更改才发生。更进一步,在更改发生时未读的所有输入数据都被删除(刷清)

使用如:tcsetattr(fd,TCSANOW,&newtio)

3. 串口使用详解

在配置完串口的相关属性后,就可对串口进行打开,读写操作了。其使用方式与文件操作一样,区别在于串口是一个终端设备。

3.1 打开串口

fd = open( "/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY);

Open函数中除普通参数外,另有两个参数O_NOCTTYO_NDELAY

O_NOCTTY: 通知linix系统,这个程序不会成为这个端口的控制终端。

O_NDELAY: 通知linux系统不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。

然后,恢复串口的状态为阻塞状态,用于等待串口数据的读入。用fcntl函数:

fcntl(fd, F_SETFL, 0;

接着,测试打开的文件描述府是否引用一个终端设备,以进一步确认串口是否正确打开。

isatty(STDIN_FILENO);

3.2 读写串口

串口的读写与普通文件一样,使用read,write函数。

read(fd,buff,8);

write(fd,buff,8);

实例见:seri.c


Linux系统调用与文件I/O

 

1Linux系统调用与文件I/O

 

1.1、 Linux系统调用

所谓系统调用是指操作系统提供给用户程序的一组“ 特殊” 特户殊接口,用过程这组序可“ 以通特殊” 接口来获得操作系统内核提供的特殊服务。

linux中用户程序不能直接访问内核提供的服务。为了更好的保护内核空间,将程序的运行空间分为内核空间和用户空间,他们运行在不同的级别上,在逻辑上是相互隔离的。

2.1、用户程序接口(API

linux中用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准—POSIX标准。这些系统调用编程接口主要通过C(libc)实现的。












可用的文件I / O函数——打开文件、读文件、写文件等等。大多数linux文件I / O只需用到5个函数:openreadwritelseek 以及close

不带缓存指的是每个r e a dw r i t e都调用内核中的一个系统调用。这些不带缓存的I / O函数不是ANSI C的组成部分,但是P O S I X 组成部分。

2.2 文件描述符

对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,o p e nc r e a t返回的文件描述符标识该文件,将其作为参数传送给r e a dw r i t e

P O S I X . 1应用程序中,整数012应被代换成符号常数STDIN_FILENOSTDOUT_FILENOSTDERR_FILENOO。这些常数都定义在头文件 <unistd.h>中。

文件描述符的范围是0 ~ OPEN_MAX 。早期的UNIX版本采用的上限值是1 9 (允许每个进程打开2 0个文件),现在很多系统则将其增加至6 3

2.3 open函数

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

int open(const char *pathname, int oflag, …/*, mode_t mode * /) ;

返回:若成功为文件描述符,若出错为- 1

O_APPEND 每次写时都加到文件的尾端。

O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权位。

O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。

O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0

O_NOCTTY 如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。

O_NONBLOCK 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。

O_SYNC 使每次w r i t e都等到物理I / O操作完成。

2.4 creat函数

可用creat函数创建一个新文件。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

int creat(const char * pathname, mode_t m o d e) ;

返回:若成功为只写打开的文件描述符,若出错为- 1

注意,此函数等效于:

open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) ;

c r e a t的一个不足之处是它以只写方式打开所创建的文件。

2.5 close函数

可用close函数关闭一个打开文件:

#include <unistd.h>

int close (int filedes);

返回:若成功为0,若出错为- 1

当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用c l o s e关闭打开的文件。

如:例open.c

cat open.c

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

#include <stdio.h>


int main(void)

{

int fd;


if((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_WRONLY,0600))<0)

{

perror("open:");

exit(1);

}

else

printf("open file:hello.c %d\n",fd);


if( close(fd) < 0)

{

perror("close:");

exit(1);

}

else

printf("Close hello.c\n");


exit(0);

}

2.6 lseek函数

每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0

可以调用l s e e k显式地定位一个打开文件。

#include <sys/types.h>

#include <unistd.h>

off_t lseek(int filesdes, off_t offset, int whence) ;

返回:若成功为新的文件位移,若出错为- 1

对参数offset 的解释与参数w h e n c e的值有关。

whenceSEEK_SET,则将该文件的位移量设置为距文件开始处offset 个字节。

whenceSEEK_CUR ,则将该文件的位移量设置为其当前值加offset,offset可为正或负。

whenceSEEK_END ,则将该文件的位移量设置为文件长度加offset,offset可为正或负。

l s e e k成功执行,则返回新的文件位移量,为此可以用下列方式确定一个打开文件的当前位移量:

off_t curr_pos;

Curr_pos = lseek(fd, 0, SEEK_CUR);

2.7 read函数

r e a d函数从打开文件中读数据

#include <unistd.h>

ssize_t read(int feledes, void *buff, size_t nbytes) ;

返回:读到的字节数,若已到文件尾为0,若出错为- 1

r e a d成功,则返回读到的字节数。如已到达文件的尾端,则返回0

有多种情况可使实际读到的字节数少于要求读字节数:

读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有3 0个字节,而要求读1 0 0个字节,r e a d返回3 0,下一次再调用r e a d,它将返回0 (文件尾端)

当从终端设备读时,通常一次最多读一行(11章将介绍如何改变这一点)

当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。

某些面向记录的设备,例如磁带,一次最多返回一个记录。

读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。

2.8 write函数

w r i t e函数向打开文件写数据。

#include <unistd.h>

ssize_t write(int filedes, const void * buff, size_t nbytes) ;

返回:若成功为已写的字节数,若出错为- 1

其返回值通常与参数nbytes的值不同,否则表示出错。w r i t e出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。

对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O _ A P P E N D选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。

见例:write.c

cat write.c

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#define MAXSIZE

int main(void)

{

int i,fd,size,len;

char *buf="Hello! I'm writing to this file!";

char buf_r[10];



len = strlen(buf);

buf_r[10] = '\0';

if((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_RDWR,0666))<0)

{

perror("open:");

exit(1);

}

else

printf("open file:hello.c %d\n",fd);



if((size = write( fd, buf, len)) < 0){

perror("write:");

exit(1);

}

else

printf("Write:%s\n",buf);



lseek( fd, 0, SEEK_SET);

if((size = read( fd, buf_r, 10))<0)

{

perror("read:");

exit(1);

}

else

printf("read form file:%s\n",buf_r);



if( close(fd) < 0)

{

perror("close:");

exit(1);

}

else

printf("Close hello.c\n");



exit(0);

}

2.9 fcntl函数

fcntl函数可以改变已经打开文件的性质。

#include <sys/types.h>

#include <unistd.h>

#include <fcntl.h>

int fcntl(int filedes, int cmd, ...) ;

返回:若成功则依赖于cmd(见下),若出错为- 1

f c n t l函数有五种功能:

复制一个现存的描述符新文件描述符作为函数值返(c m dF_DUPFD)。

获得/设置文件描述符标记,对应于filedes 的文件描述符标志作为函数值返回.(c m d = F_GETFDF_SETFD)。

获得/设置文件状态标志,对应于filedes 的文件状态标志作为函数值返回。(c m d = F_GETFLF_SETFL)。

获得/设置异步I / O有权(c m d = F_GETOWNF_SETOWN)。

获得/设置记录锁(c m d = F_SETLK , F_SETLKW)。

文件状态

标志说明

O _ R D O N L Y

只读打开

O _ W R O N L Y

只写打开

O _ R D W R

/写打开

O _ A P P E N D

写时都添加至文件尾

O _ N O N B L O C K

非阻塞方式

O _ S Y N C

等待写完成

O _ A S Y N C

异步I / O


F_SETFL 将文件状态标志设置为第三个参数的值(取为整型值)。可以更改的几个标志是:O _ A P P E N D,O _ N O N B L O C K,O _ S Y N CO _ A S Y N C

F_GETOWN 取当前接收S I G I OS I G U R G信号的进程I D或进程组I D

F_SETOWN 设置接收S I G I OS I G U R G信号的进程I D或进程组I D。正的a rg指定一个进 程I D,负的a rg表示等于a rg绝对值的一个进程组I D

2.9.2 fcntl给文件加锁

当多个用户共同使用、操作一个文件的时候,linux通常采用的方法是给文件上锁,来避免共享资源产生竞争的状态。

文件锁包括建议锁和强制性锁。建议性锁要求上锁文件的进程都要检测是否有锁存在,并尊重已有的锁。强制性锁由内核和系统执行的锁。

Fcntl不仅可以实施建议性锁而且可以实施强制性锁。

2.9.3 fcntl函数格式

#include <sys/types.h>

#include <unistd.h>

#include <fcnt1.h>

int fcnt1(int filedes, int cmd,struct flock flockptr) ;

struct flock 结构

struct flock{

short l_type; /*F_RDLCK,F_WRLCK,or F_UNLCK*/

off_t l_start; /*offset in bytes, relative to l_whence*/

short l_whence; /*SEEK_SET,SeeK_CUR,or SEEK_END*/

off_t l_len; /*length,in bytes;0 means lock to EOF */

pid_t l_pid; /*returned with F_GETLK*/

};

2.9.4 f l o c k结构说明:

所希望的锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)要加锁或解锁的区域的起始地址,由l_startl_whence两者决定。l_stat是相对位移量(字节),l_whence则决定了相对位移量的起点。

区域的长度,由l_len表示。

关于加锁和解锁区域的说明还要注意下列各点:

该区域可以在当前文件尾端处开始或越过其尾端处开始,但是不能在文件起始位置之前开始或越过该起始位置。

如若l_len0,则表示锁的区域从其起点(由l_startl_whence决定)开始直至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。

为了锁整个文件,通常的方法是将l_start说明为0l_whence说明为SEEK_SETl_len说明为0

2.10 ioctl函数

ioctl 函数是I / O操作的杂物箱。不能用本章中其他函数表示的I / O操作通常都能用i o c t l表示。终端I / Oioctl 的最大使用方面,主要用于设备的I / O控制。

#include <unistd.h> /* SVR4 */

#include <sys/ioctl.h> /* 4.3+BSD * /

int ioctl(int filedes, int request, . . . ) ;

返回:若出错则为- 1,若成功则为其他值。

3select 实现I/O复用

 

3.1 I/O处理的五种模型

阻塞I/O模型:若所调用的I/O函数没有完成相关的功能就会使进程挂起,直到相关数据到达才会返回。如:终端、网络设备的访问。

非阻塞模型:当请求的I/O操作不能完成时,则不让进程休眠,而且返回一个错误。如:openreadwrite访问。

I/O多路转接模型:如果请求的I/O 操作阻塞,且他不是真正阻塞I/O,而且让其中的一个函数等待,在这期间, I/O还能进行其他操作。如:select函数。

异步I/O模型:在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核。由内核进行后续处理,这种用法现在较少。

信号驱动I/O模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O

3.2 select函数

传向select的参数告诉内核:

(1) 我们所关心的描述符。

(2) 对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的描述符?是否关心一个描述符的异常条件?)。

(3) 希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)。

s e l e c t返回时,内核告诉我们:

(1) 已准备好的描述符的数量。

(2) 哪一个描述符已准备好读、写或异常条件。

#include <sys/types.h>/* fd_set data type */

#include <sys/time.h> /* struct timeval */

#include <unistd.h> /* function prototype might be here */

int select (int numfds, fd_set *readfds,fd_set *writefds, fd_set *exceptfds,struct timeval * timeout) ;

返回:准备就绪的描述符数,若超时则为0,若出错则为- 1

timeout值:

NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止;

具体值: struct timeval 类型的指针,若等待为timeout时间还没有文件描述符准备好,就立即返回;

0:从不等待,测试所有指定的描述符并立即返回;

先说明最后一个参数,它指定愿意等待的时间。

struct timeval

{

long tv_sec; /* seconds */

long tv_usec; /* and microseconds */

}

select函数根据希望进行的文件操作对文件描述符进行分类处理,这里,对文件描述符的处理主要设计4个宏函数:

FD_ZERO(fd_set *set) 清除一个文件描述符集;

FD_SET(int fd, fd_set *set) 将一个文件描述符加入文件描述符集中;

FD_CLR(int fd, fd_set *set) 将一个文件描述符从文件描述符集中清除;

FD_ISSET(int fd, fd_set *set) 测试该集中的一个给定位是否有变化;

在使用select函数之前,首先使用FD_ZEROFD_SET来初始化文件描述符集,并使用select函数时,可循环使用FD_ISSET测试描述符集,在执行完成对相关的文件描述符后, 使用FD_CLR来清除描述符集。

例,见:select.c

cat select.c

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/time.h>


int main(void)

{

int fds[2];

char buf[7];

int i,rc,maxfd;

fd_set inset1,inset2;

struct timeval tv;


if((fds[0] = open ("hello1", O_RDWR|O_CREAT,0666))<0)

perror("open hello1");

if((fds[1] = open ("hello2", O_RDWR|O_CREAT,0666))<0)

perror("open hello2");

if((rc = write(fds[0],"Hello!\n",7)))

printf("rc=%d\n",rc);


lseek(fds[0],0,SEEK_SET);

maxfd = fds[0]>fds[1] ? fds[0] : fds[1];


FD_ZERO(&inset1);

FD_SET(fds[0],&inset1);

FD_ZERO(&inset2);

FD_SET(fds[1],&inset2);

tv.tv_sec=2;

tv.tv_usec=0;

while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2))

{

if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0)

perror("select");

else{

if(FD_ISSET(fds[0],&inset1))

{

rc = read(fds[0],buf,7);

if(rc>0)

{

buf[rc]='\0';

printf("read: %s\n",buf);

}else

perror("read");

}

if(FD_ISSET(fds[1],&inset2))

{

rc = write(fds[1],buf,7);

if(rc>0)

{

buf[rc]='\0';

printf("rc=%d,write: %s\n",rc,buf);

}else

perror("write");

sleep(10);

}

}

}

exit(0);

}


原创粉丝点击