管道:
管道是通过pipe函数创建的:
#include <unistd.h>
int pipe(int fd[2]);
返回值:成功0,失败-1
fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入
fstat函数对管道的每一段都返回一个FIFO类型的文件描述符。可以用S_ISFIFO宏测试管道。
程序创建一个从父进程到子进程的管道,并且父进程经由管道向子进程传送数据:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if(pipe(fd)<0)
printf("pipe error\n");
if((pid=fork())<0){
printf("fork error\n");
}
else if(pid>0){
close(fd[0]);
write(fd[1],"hello wordld\n",12);
}
else
{
close(fd[1]);
n=read(fd[0],line,MAXLINE);
write(STDOUT_FILENO,line,n);
}
exit(0);
}
经由管道从父进程向子进程传送数据
#include "apue.h"
#include <sys/wait.h>
#define DEF_PAGER "/bin/more"
int main(int argc,char *argv[])
{
int n;
int fd[2];
pid_t pid;
char *pager,*argv0;
char line[MAXLINE];
FILE *fp;
if(argc!=2)
printf("usage :a.out<pathname>\n");
if((fp=fopen(argv[1],"r"))==NULL)
printf("can't open %s\n",argv[1]);
if(pipe(fd)<0)
printf("pipe error\n");
if((pid=fork())<0)
printf("fork error\n");
else if(pid>0)
{
close(fd[0]);
while(fgets(line,MAXLINE,fp)!=NULL){
n=strlen(line);
if(write(fd[1],line,n)!=n)
printf("write error to pipe\n");
}
if(ferror(fp))
printf("fgets error\n");
close(fd[1]);
if(waitpid(pid,NULL,0)<0)
printf("waitpid error\n");
exit(0);
}
else
{
close(fd[1]);
if(fd[0]!=STDIN_FILENO){
if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)
{
printf("dup2 error to stdin\n");
}
close(fd[0]);
}
if((pager=getenv("PAGER"))==NULL)
paget=DEF_PAGER;
if((argv0=sttchr(pager,'/'))!=NULL)
argv0++;
else
argv0=pager;
if(execl(pager,argv0,(char *)0)<0)
printf("execl error for %s",pager);
}
exit(0);
}
将文件复制到分页程序。
函数的管道的实现:
#include "apue.h"
static int pfd1[2],pfd2[2];
void TELL_WAIT(void)
{
if(pipe(pfd1)<0 || pipe(pfd2)<0)
printf("pipe error\n");
}
void TELL_PARENT(pid_t pid)
{
if(write(pfd2[1],"c",1)!=1)
printf("write error\n");
}
void WAIT_PARENT(void)
{
char c;
if(read(pfd1[0],&c,1)!=1)
printf("read error\n");
if(c!='p')
printf("WAIT_PARENT:incorrect data\n");
}
void WAIT_CHILD(void)
{
char c;
if(read(pfd2[0],&c,1)!=1)
printf("read error\n");
if(c!='c')
printf("WAIT_CHILD:incorrect data\n");
}
让父进程和子进程同步的例程
我们在调用fork之前创建了两个管道。父进程在调用TELL_CHILD时,经由上一个管道写一个字符“p”,子进程在调用TELL_PARENT时,
经由下一个管道写一个字符“c”。相应的WAIT_XXX函数调用read读一个字符,没有读到字符时则阻塞(休眠等待)。
父进程 子进程
pfd1[1]---------"p"-------------->pfd1[0]
pfd2[0]<--------"c"---------------pfd2[1]
函数popen和pclose:
常见的操作时创建一个连接到另一个进程的管道,然后读其输出或者向其输入端发送数据,为此,标准I/O库提供了两个函数popen和
pclose。这两个函数实现的操作是:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令
终止。
#include <stdio.h>
FILE *popen(const char *cmdstring,const char *type);
返回值:成功,返回文件指针,出错,返回NULL
int pclose(FILE *fp);
返回值:成功,返回cmdstring的终止状态,出错,返回-1
函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。
如果type是“r”,则文件指针连接到cmdstring的标准输出。
如果type是“w”,则文件指针连接到cmdstring的标准输入。
父进程 cmdstring(子进程)
fp <---------------------- stdout
执行fp=popen(cmdstring,"r")
父进程 cmdstring(子进程)
fp ----------------------> stdin
执行fp=popen(cmdstring,"w")
pclose函数关闭标准I/O流,等待命令终止,然后返回shell的终止状态。如果shell不能被执行,则pclose返回的终止状态与shell已
执行exit一样。
cmdstring由Bourne shell以下列方式执行:
sh -c cmdstring
这表示shell将扩展cmdstring中的任何特殊字符。例如,可以使用:
fp=popen("ls *.c","r");
或者
fp=popen("cmd 2>&1","r");
用popen重写程序:
#include "apue.h"
#include <sys/wait.h>
#define PAGER "${PAGER:-more}"
int main(int argc,char *argv[])
{
char line[MAXLINE];
FILE *fpin,*fpout;
if(argc!=2)
printf("usage:a.out <pathanme>\n");
if((fpin=fopen(argv[1],"r"))==NULL)
printf("can't open %s",argv[1]);
if((fpout=popen(PAGER,"w"))==NULL)
printf("popen error\n");
while(fgets(line,MAXLINE,fpin)!=NULL)
{
if(fputs(line,fpout)==EOF)
printf("fputs error to pipe\n");
}
if(ferror(fpin))
printf("fgets error\n");
if(pclose(fpout)==-1)
printf("pclose error\n");
exit(0);
}
用popen向分页程序传送文件
shell命令${PAGER:-more}的意思是:如果shell变量PAGER已经定义,且其值非空,则使用其值,否则使用字符串more。
编写的popen和pclose:
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
/*
*在运行时分配给数组的指针。
*/
static pid_t *childpid = NULL;
/*
*从我们的open_max()
*/
static int maxfd;
FILE *
popen(const char *cmdstring, const char *type)
{
int i;
int pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" or "w" */
if((type[0] != 'r' && type[0] != 'w') || type[1] != 0)
{
errno = EINVAL; /* required by POSIX */
return(NULL);
}
if(childpid == NULL) /* 第一次通过 */
{
/* allocate zeroed out array for child pids */
maxfd = open_max();
if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if(pipe(pfd) < 0)
return(NULL); /* errno set by pipe() */
if((pid = fork()) < 0)
{
return(NULL); /* error set by fork() */
}
else if(pid == 0)
{
if(*type == 'r')
{
close(pfd[0]);
if(pfd[1] != STDOUT_FILENO)
{
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
}
else
{
close(pfd[1]);
if(pfd[0] != STDIN_FILENO)
{
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for(i=0; i < maxfd; i++)
if(childpid[i] > 0)
close(i);
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
/* parent continues... */
if(*type == 'r')
{
close(pfd[1]);
if((fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
}
else
{
close(pfd[0]);
if((fp = fdopen(pfd[1], type)) == NULL)//fdopen取一个现存的文件描述符,并使一个标准的I / O流与该描述符相结合
return(NULL);
}
childpid[fileno(fp)] = pid; /* remeber child pid for this fd */
return(fp);
}
int
pclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if(childpid == NULL)
{
errno = EINVAL;
return(-1); /* popen() has never been called */
}
fd = fileno(fp);//fileno()用来取得参数stream指定的文件流所使用的文件描述符
if((pid = childpid[fd]) = 0)
{
errno = EINVAL;
return(-1); /* fp wasn't opened by popen() */
}
childpid[fd] = 0;
if(fclose(fp) == EOF)
return(-1);
while(waitpid(pid, &stat, 0) < 0)
if(errno != EINTR)
return(-1); /* error other than EINTR from waitpid() */
return(stat); /* return child's termination status */
}
一个简单的用于演示这个操作的过滤程序。它将标准输入复制到标准输出,在复制时将大写字符变换未小写字符。在写完换行符之后,要仔细
冲洗(fflush)标准输出。
#include "apue.h"
#include <ctype.h>
int main(void)
{
int c;
while((c=getchar())!=EOF){
if(isupper(c))
c=tolower(c);
if(putchar(c)==EOF)
printf("output error\n");;
if(c=='\n')
fflush(stdout);
}
exit(0);
}
编译为myuclc,然后使用popen调用它:
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
char line[MAXLINE];
FILE *fpin;
if((fpin=popen("myuclc","r"))==NULL)
printf("popen error\n");
for(;;){
fputs("prompt>",stdout);
fflush(stdout);
if(fgets(line,MAXLINE,fpin)==NULL)
break;
if(fputs(line,stdout)==EOF)
printf("fputs error to pipe\n");
}
if(pclose(fpin)==-1)
printf("pclose error\n");
putchar('\n');
exit(0);
}
因为标准输出常常时行缓冲的,而提示并不包含换行符,所以在写了提示之后,需要调用fflush。
协同进程:
UNIX系统过滤程序从标准输入读取数据,向标准输出写数据。当一个过滤程序既产生某个过滤程序的输入,
又读取该过滤程序的输出时,它就变成了协同进程。
一个简单的协同进程,它从其标准输入读取两个数,计算它们的和,然后将和写至其标准输出。
#include "apue.h"
int main(void)
{
int n,int1,int2;
char line[MAXLINE];
while((n=read(STDIN_FILENO,line,MAXLINE))>0){
line[n]=0;//null终止
if(sscanf(line,"%d%d",&int1,&int2)==2){
sprintf(line,"%d\n",int1+int2);
n=strlen(line);
if(write(STDOUT_FILENO,line,n)!=n)
printf("write error\n");
}
else{
if(write(STDOUT_FILENO,"invalid args\n",13)!=13)
printf("write error\n");
}
}
exit(0);
}
将两个数相加的简单过滤程序。
对此程序进程编译,将其可执行代码存入名为add2的文件。
程序从其标准输入读取两个数之后调用add2协同进程,并将协同进程送来的值写到其标准输出中。
#include "apue.h"
static void sig_pipe(int);
int main(void)
{
int n,fd1[2],fd2[2];
pid_t pid;
char line[MAXLINE];
if(signal(SIGPIPE,sig_pipe)==SIG_ERR)
printf("signal error\n");
if(pipe(fd1)<0 || pipe(fd2)<0)
printf("pipe error\n");
if((pid=fork())<0)
printf("fork error\n");
else if(pid>0)//parent
{
close(fd1[0]);
close(fd2[1]);
while(fgets(line,MAXLINE,stdin)!=NULL)
{
n=strlen(line);
if(write(fd1[1],line,n)!=n)
printf("write error to pipe\n");
if((n=read(fd2[0],line,MAXLINE))<0)
printf("read error from pipe\n");
if(n==0)
{
printf("child closed pipe\n");
break;
}
line[n]=0;
if(fputs(line,stdout)==EOF)
printf("fputs error\n");
}
if(ferror(stdin))
printf("fgets error on stdin\n");
exit(0);
}
else{//child
close(fd1[1]);
close(fd2[0]);
if(fd1[0]!=STDIN_FILENO)
{
if(dup2(fd1[0],STDIN_FILENO)!=STDIN_FILENO)
printf("dup2 error to stdin\n");
close(fd1[0]);
}
if(fd2[1]!=STDOUT_FILENO){
if(dup2(fd2[1],STDOUT_FILENO)!=STDOUT_FILENO)
printf("dup2 error to stdout\n");
close(fd2[1]);
}
if(execl("./add2","add2",(char*)0)<0)
printf("execl error\n");
}
exit(0);
}
static void sig_pipe(int signo)
{
printf("SIGPIPE caught\n");
exit(1);
}
父进程:
fd1 fd2
0 关闭读 读
1 写 关闭写
子进程:
fd1 fd2
0 读 关闭读
1 关闭写 写
如果使用标准I/O改写该协同进程:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define MAXLINE 1024
int main(void)
{
int int1,int2;
char line[MAXLINE];
while(fgets(line,MAXLINE,stdin)!=NULL)
{
if(sscanf(line,"%d%d",&int1,&int2)==2)
{
if(printf("%d\n",int1+int2)==EOF)
printf("printf error\n");
}
else{
if(printf("invalid args\n")=EOF)
printf("printf error\n");
}
}
exit(0);
}
FIFO:
#include <sys/stat.h>
int mkfifo(const char *path,mode_t mode);
int mkfifoat(int fd,const char *path,mode_t mode);
返回值:成功0,失败-1
XSI IPC:
ftok提供的唯一服务就是由一个路径名和项目ID产生一个键:
#include <sys/ipc.h>
key_t ftok(const char *path,int id);
返回值:成功,返回键,出错,返回(key_t)-1
path参数必须引用一个现有的文件。当产生键时,只使用id参数的低8位。
消息队列:
msgget用于创建一个新队列或者打开一个现有队列。
msgsnd将新消息添加到队列尾端。
每个消息包含一个正的长整型类型的字段、一个非负的长度以及实际数据字节数,所有这些都在将消息添加
到队列时,传送给msgsnd。
msgrcv从队列中取消息。
每个队列都有一个msqid_ds结构于其相关联:
struct msqid_ds{
struct ipc_perm msg_perm;
msgqnum_t msg_qnum;//队列上的消息
msglen_t msg_qbytes;//队列中的最大字节数
pid_t msg_lspid;//pid of last msgsnd
pid_t msg_lrpid;//pid of last msgrcv
time_t msg_stime;//last-msgsnd time
time_t msg_rtime;//last-msgrcv time
time_t msg_ctime;//last change time
};
#include <sys/msg.h>
int msgset(key_t key,int flag);
返回值:成功,返回消息队列ID,出错,返回-1
#include <sys/msg.h>
int msgctl(int msgid,int cmd,struct msgid_ds *buf);
返回值:成功,返回0,出错,返回-1
cmd参数指定对msgid指定的队列要执行的命令:
IPC_STAT 取此队列的msgid_ds结构,并将它存放在buf执行的结构中
IPC_SET 将字段msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes
从buf指向的结构复制到与这个队列相关的msgid_ds结构中。
IPC_RMID 从系统中删除该消息队列以及仍在该队列中的所有数据。
调用msgsnd将数据放到消息队列中:
#include <sys/msg.h>
int msgsnd(int msgid,const void *ptr,size_t nbytes,int flag);
返回值:成功0,失败-1
ptr指向mymesg结构的指针:
struct mymesg{
long mtype;
char mtext[[512];
};
msgrcv从队列中取用消息:
#include <sys/msg.h>
ssize_t msgrcv(int msgid,void *ptr,size_t nbytes,long type,ing flag);
返回值:成功,返回消息数据部分的长度,出错,返回-1
信号量:
调用函数semget来获得一个信号量ID:
#include <sys/sem.h>
int semget(key_t key,int nsems,int flag);
返回值:成功,返回信号量ID,出错,返回-1
信号量的相关操作:
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd,...);
函数semop自动执行信号量集合上的操作数组:
#include <sys/sem.h>
int semop(int semid,struct sembuf semoparray[],size_t mops);
参数semoparray是一个指针,它指向一个由sembuf结构表示的信号量操作数组。
struct sembuf
{
unsigned short int sem_num; /* 信号量数 */
short int sem_op; /* 信号量操作 */
short int sem_flg; /* 操作标志 */
};
共享存储:
允许两个或者多个进程共享一个给定的存储区。
struct shmid_ds{
struct ipc_perm shm_perm;/* 操作权限*/
int shm_segsz; /*段的大小(以字节为单位)*/
time_t shm_atime; /*最后一个进程附加到该段的时间*/
time_t shm_dtime; /*最后一个进程离开该段的时间*/
time_t shm_ctime; /*最后一个进程修改该段的时间*/
unsigned short shm_cpid; /*创建该段进程的pid*/
unsigned short shm_lpid; /*在该段上操作的最后1个进程的pid*/
short shm_nattch; /*当前附加到该段的进程的个数*/
/*下面是私有的*/
unsigned short shm_npages; /*段的大小(以页为单位)*/
unsigned long *shm_pages; /*指向frames->SHMMAX的指针数组*/
struct vm_area_struct *attaches; /*对共享段的描述*/
};
struct ipc_perm
{
key_t key; //调用shmget()时给出的关键字
uid_t uid; /*共享内存所有者的有效用户ID */
gid_t gid; /* 共享内存所有者所属组的有效组ID*/
uid_t cuid; /* 共享内存创建 者的有效用户ID*/
gid_t cgid; /* 共享内存创建者所属组的有效组ID*/
unsigned short mode; /* Permissions + SHM_DEST和SHM_LOCKED标志*/
unsignedshort seq; /* 序列号*/
};
调用的第一个函数通常是shmget,它获得一个共享存储标识符:
#include <sys/shm.h>
int shmget(key_t key,size_t size,int flag);
返回值:成功,返回共享存储ID,出错,返回-1
shmctl函数对共享存储段执行多种操作:
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
返回值:成功,返回0,出错,返回-1
一旦创建一个共享存储段,进程就可以调用shmat将其连接到它的地址空间中:
#include <sys/shm.h>
void *shmat(int shmid,const void *addr,int flag);
返回值:成功,返回指向共享存储段的指针,失败,返回-1
把共享内存区对象映射到调用进程的地址空间:
#include <sys/shm.h>
void *shmat(int shmid, const void *addr, int flg)
成功:附加好的共享内存地址.出错:-1,错误原因存于errno中
shmid
共享内存标识符
shmaddr
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg
SHM_RDONLY:为只读模式,其他为读写模式
shmdt(断开共享内存连接)
#include <sys/shm.h>
int shmat(const void *addr);
addr参数是调用shmat时的返回值。如果成功,shmat将使相关shmid_ds结构中的shm_nattch计数器值减1
程序打印了一些特定系统存放各种类型的数据的位置信息:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE 0600 //user read/write
char array[ARRAY_SIZE];
int main(void)
{
int shmid;
char *ptr,*shmptr;
printf("array[] from %p to %p\n",(void*)&array[0],(void*)&array[ARRAY_SIZE]);
printf("stack around %p\n",(void*)&shmid);
if((ptr=malloc(MALLOC_SIZE))==NULL)
printf("mallc error\n");
printf("malloced from %p to %p\n",(void*)ptr,(void *)ptr+MALLOC_SIZE);
if((shmid=shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE))<0)
printf("shmat error\n");
if((shmptr=shmat(shmid,0,0))==(void*)-1)
printf("shmat error\n");
printf("shared memory attached from %p to %p\n",(void *)shmptr,(void*)shmptr+SHM_SIZE);
if(shmctl(shmid,IPC_RMID,0)<0)
printf("shmctl error\n");
exit(0);
}
打印各种类型的数据存放的位置
$ ./exaple9
array[] from 0x6010a0 to 0x60ace0
stack around 0x7fffaa3a79fc
malloced from 0x1ba4010 to 0x1bbc6b0
shared memory attached from 0x7fdf3f8a9000 to 0x7fdf3f8c16a0
/dev/zero的存储映射:
共享存储可由两个不相关的进程使用。但是,如果进程是相关的,则某些实现提供了一种不同的技术。
在读取设备/dev/zero时,该设备是0字节的无限资源。它也接受写向它的任何数据,但又忽略这些数据。我们对此设备作为IPC的兴趣在于,
当对其进行存储映射时,它具有一些特殊性质:
1、创建一个未命名的存储区,其长度时mmap的第二个参数,将其向上取整为系统的最近页长。
2、存储区都初始化为0
3、如果多个进程的共同祖先进程对mmap指定了MAP_SHARED标志,则这些进程可共享此存储区
原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
mmap将一个文件或者其它对象映射进内存。
文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。
mmap在用户空间映射调用系统中作用很大。
程序是使用此特殊设备的一个例子:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#define NLOOPS 1000
#define SIZ sizeof(long)/*共享内存区域的大小*/
static int update(long *ptr)
{
return((*ptr)++);/*增量前返回值*/
}
void TELL_WAIT(void)
{
if(pipe(pfd1)<0 || pipe(pfd2)<0)
printf("pipe error\n");
}
int main(void)
{
int fd,i,counter;
pid_t pid;
void *area;
if((fd=open("/dev/zero",O_RDWR))<0)
printf("open error\n");
//成功执行时,mmap()返回被映射区的指针
if((area=mmap(0,SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
printf("mmap error\n");
close(fd);
TELL_WAIT();
if((pid=fork())<0)
printf("fork error\n');
else if(pid>0)/*parent*/
{
for(i=0;i<NLOOPS;i+=2)
{
if((counter=update((long*)area))!=i)
printf("parent:expected %d ,got %d",i,counter);
TELL_CHILD(pid);
WAIT_CHILD();
}
}
else{//child
for(i=1;i<NLOOPS+1;i+=2){
WAIT_PARENT();
if((counter=update((long*)area))!=i)
printf("child:expected %d,got %d",i,counter);
TELL_PARENT(getppid());
}
}
exit(0);
}
在父进程、子进程之间使用/dev/zero的存储映射I/O的IPC
匿名存储映射:
为使上图中的程序应用这个设施,我们对它进行3处修改:
a.删除了/dev/zero的open语句
b.删除了fd的close语句
c.将mmap调用修改如下:
if((area=mmap(0,SIZE,PROT_READ | PROT_WRITE,MAP_ANON | MAP_SHARED,-1,0))==MAP_FAILED)
此调用执行了MAP_ANON标志,并将文件描述符设置为-1.
POSIX信号量:
3种IPC机制源于POSIX.1的实时扩展。Single UNIX Specificationa将3种机制(消息队列、信号量和共享存储)置于可选部分中。
在SUSv4之前,POSIX信号量接口已经被包含在信号量选项中。在SUSv4中,这些接口被移至基本规范,而消息队列和共享存储接口依然
是可选的。
我们可以调用sem_open函数来创建一个新的命名信号量或者使用一个现有信号量:
#include <semaphore.h>
sem_t *sem_open(const char *name,int oflag,.../*mode_t mode,unsigned int value*/);
返回值:成功,返回指向信号量的指针,出错,返回SEM_FAILED
存在一个现有的命名信号量时:我们只指定两个参数,信号量的名字和oflag参数的0值
当oflag参数有O_CREAT标志集时,如果命名信号量不存在,则创建一个新的。
如果它已经存在,则会被使用,但是不会有额外的初始化发生。
调用sem_close函数来释放任何信号量相关的资源:
#include <semaphore.h>
int sem_close(sem_t *sem);
返回值:成功,返回0,出错,返回-1
如果进程没有首先调用sem_close而退出,那么内核将自动关闭任何打开的信号量。
注意,这不会影响信号量的状态---如果已经对它进行了增1操作,并不会仅因为退出而改变。
sem_unlink函数来销毁一个命名信号量:
#include <semaphore.h>
int sem_unlink(const char *name);
返回值:成功,返回0,失败,返回-1
sem_unlink函数删除信号量的名字。
如果没有打开的信号量引用,则该信号量会被销毁。
否则,销毁会延迟到最后一个打开的引用关闭。
可以使用sem_wait或者sem_trywait函数来实现信号量的减1操作:
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
返回值:成功0,失败,-1
使用sem_wait函数时,如果信号量计数是0就会被阻塞。直到成功使信号量减1或者被信号中断才返回。
可以使用sem_trywait函数来避免阻塞。
调用sem_trywait时,如果信号量是0,则不会阻塞,而是会返回-1并且将errno置为EAGAIN。
第三个选择是阻塞一段确定的时间。为此,可以使用sem_timewait函数:
#include <semaphore.h>
#include <time.h>
int sem_timewait(sem_t *restrict sem,const struct timespec *restrict tsptr);
返回值:成功0,失败,-1
调用sem_post函数使信号量值增1:
#include <semaphore.h>
int sem_post(sem_t *sem);
返回值:成功,0,出错,返回-1
调用sem_init函数创建一个未命名的信号量:
#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
返回值:成功0,失败-1
pshared参数表明是否在多个进程中使用信号量。如果是,将其设置成一个非0值。
value参数指定了信号量的初始值
sem_t类型的变量并把它的地址传递给sem_init来实现初始化,而不是像sem_open函数那样返回一个指向信号量的指针。
如果要在两个进程之间使用信号量,需要确保sem参数指向两个进程之间共享的内存范围。
调用sem_destroy函数丢弃它:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
返回值:成功,0,失败,-1
调用sem_destroy后,不能再使用任何带有sem的信号量函数,除非通过调用sem_init重新初始化它。
sem_getvalue函数可以用来检索信号量值:
#include <semaphore.h>
int sem_getvalue(sem_t *restrict sem,int *restrict valp);
返回值:成功,0,失败,-1
成功后,valp指向的整数值将包含信号量值。
程序展示了基于信号量的互斥原语的实现:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
struct slock{
sem_t *semp;
char name[_POSIX_NAME_MAX];
}
struct slock * s_alloc(){
struct slock *sp;
static int cnt;
if((sp=malloc(sizeof(struct slock)))==NULL)
return(NULL);
do{
snprintf(sp->name,sizeof(sp->name),"/%ld.%d",(long)getpid(),cnt++);
sp->semp=sem_open(sp->name,O_CREAT|O_EXCL,S_IRWXU,1);
}while((sp->semp==SEM_FAILED)&&(errno==EEXIST));
if(sp->semp==SEM_FAILED){
free(sp);
return(NULL);
}
sem_unlink(sp->name);
return(sp);
}
void s_free(struct slock *sp)
{
sem_close(sp->semp);
free(sp);
}
int s_lock(struct slock *sp)
{
return(sem_wait(sp->semp));
}
int s_trylock(struct slock *sp)
{
return(sem_trywait(sp->semp));
}
int s_unlock(struct slock *sp)
{
return(sem_post(sp->semp));
}
根据进程ID和计数器来创建名字。我们不会可以用互斥量区保护计数器,因为当两个竞争的线程同时调用s_alloc并以
同一个名字结束时,在调用sem_open中使用O_EXCL标志将会使其中一个线程成功而另一个线程失败,失败的线程会将
errno设置未EEXIST,所以对于这种情况,我们只是再次尝试。
进程间通信
最新推荐文章于 2023-03-08 21:20:53 发布