【linux草鞋应用编程系列】_3_ 进程间通信
2013-12-13 18:02
399 查看
一、进程间通信
linux下面提供了多种进程间通信的方法, 管道、信号、信号量、消息队列、共享内存、套接字等。下面我们分别
介绍管道、信号量、消息队列、共享内存。
信号和套接字在后续介绍。
1、管道
管道又分为无名管道、命名管道。 无名管道用于父子进程间通信, 而命名管道则可以用于同一计算机上运行的
两个进程间的通信。管道可以用类似“水管”的原理来理解。
1)无名管道
要在父子进程进程间使用管道进行通信,那么先需要创建管道, 在linux中使用 pipe()创建管道。其原型如下:
返回值:
成功创建管道返回0; 失败则返回-1;
管道分为两端,一端用来写,另一端则用来读(可以想象为水管,位置高的一端进水,位置低的一端将水流出去) 。
pipe()函数的输出参数 filedes[0] 用于读取数据, filedes[1] 用于写入数据。
管道的操作和普通文件的操作一样, 但是要注意,读的时候要将写端关闭,写的时候要将读端关闭。
Exp: pipe.c 首先测试从父进程给子进程写数据。
程序执行情况如下:
利用系统调用 read()、write()进行操作的时候管道默认是阻塞的,如果管道没有数据可读,那么read( )函数就
阻塞,直到有数据读才返回。
Exp: pipe.c 子进程写入数据到管道,父进程从管道读取数据
执行结果如下:
2)命名管道
命名管道用于系统中两个进程之间通信;命名管道可用于系统中两个没有亲缘关系的进程进行通信(也可以用于父
子进程间的通信) 。
要使用命名管道,则需要创建命名管道,用函数 mkfifo () 创建命名管道。其原型如下:
返回值:
成功创建管道文件返回0, 失败返回 -1.
创建号管道特殊文件后,就可以和访问普通文件一样访问管道特殊文件。
Exp: 测试命名管道 pipe-w.c 创建命令管道并向管道写入数据
pipe-r.c 打开pipe-w.c 文件,并且从命名管道读取数据:
程序的执行结果如下:
pipe-w.c 生成wp , pipe-r.c 生成rp
要点:
在进程操作管道的时候, write 和 read 都是阻塞的; 如果写的数据没有被读取走,那么就会写的进程就会
在 write 函数阻塞; 如果读数据的时候,管道没有数据,那么就会等待管道里面别写入数据,进程在read 函数阻塞。
2、消息队列
消息队列也是linux下进程间通信的一种方式, 如果要使用消息队列在进程间进行通信,必须创建一个消息队列
或者打开一个已经存在的消息队列。
要打开一个已经存在的消息队列,或者创建一个新的消息队列,则必须先获取一个关于消息队列的IPC键值;通过函
数 ftok( )获取消息队列的IPC键值。
ftok 的原型如下:
返回值:
成功返回 IPC键值, 失败返回-1.
要点:
如果文件名和工程号一致,内核保证在任何进程中都将得到同样的 IPC 键值。
有了消息队列的IPC键值后,就是创建或者打开消息队列, 通过 msgget( ) 创建或打开一个消息队列, 其原型如下:
返回值:
如果成功,返回消息队列的ID号, 失败返回 -1。
创建了消息队列或者打开已经消息队列后,就需要完消息队列中添加消息,即发送消息; 发送消息通过函数 msgsnd( )
实现。
当发送完消息后,就可以从消息队列中获取消息,从消息队列中读取消息用函数 msgrcv( )实现。
原型如下:
消息队列使用完后,需要删除消息队列,通过 msgctl 函数实现,这是一个与ioctl 函数类似的函数,其原型如下:
返回值:
成功删除(cmd=IPC_RMID)返回0 ,失败返回-1.
Exp: 发送消息的源文件: msgsnd.c
接收消息的源代码文件: msgrcv.c
测试结果如下:
3、信号量
信号量主要用于两个进程间同步的,一般用于多进程间的同步操作。
例如当两个进程同时要访问声卡的时候,那么就需要控制声卡先由那个进程操作,等第一个进程操作完后,其他进程
才能进行操作; 但是为什么我们可以同时用mplayer 还能同时使用kmplyer播放音乐呢? 从用户的角度来看,确实是这
样的,但是从硬件的角度来看,在某一时刻声卡就只能为一个应用层程序服务,当两个应用程序同时对声卡进行操作时就
会出现异常,为了防止这种异常,需要进行对两个进程进行控制,当有一个进程获取声卡的控制权后,另外的一个进程就
不能在同一时刻访问声卡,这就是互斥操作。(可以这样测试, 在windows的操作系统上安装VM虚拟机,打开windows
media player, 然后启动虚拟机(设置虚拟机在启动的时候自动挂载声卡设备),这时候声卡就会工作不正常,会出现
一小段时间的异常, 声音不正常,这就是出现两个应用程序同时使用声卡出现的异常)。
信号量就是为解决类似的问题而设计的,信号量用来控制应用程序在同一时刻对某一系统资源的访问(这个系统资源
也称作临界资源,访问临界资源的代码,也称作临界区代码)。
信号量用结构体 struct sembuf 描述,其定义如下:
这里有一点需要说明: 通常对信号量进行操作分为 P操作、V操作,当设置 sem_op = -1 时表示进行P操作,
sem_op = 1 时表示要进行V操作。
和消息队列一样,要使用信号量,首先需要获取一个用于信号量到IPC键值, 用 ftok( ) 函数获取。 获取到用于
信号量到IPC键值后,还需要创建或者打开一个已经存在的信号量,通过打开或创建信号量获取一个关于信号量的
信号量ID;然后通过对信号量ID进行操作,就可以使用信号量。
通过semget()函数创建或打开一个信号量,并获取关于信号量的ID; 原型如下:
在获取信号量集合ID后,需要对信号量进行一些设定(或者说信号量初始化),然后才能操作,通过函数 semctl( )对
信号量进行初始化操作; setctl( )的原型如下:
返回值:
如果成功返回0 ,失败返回-1.(操作为 IPC_GETVAL, 成功返回信号量到值 )。
SETVAL: 对信号量进行设置。 这时候,要传递4个参数, 最后一个可变参数,要定义一个类型如下:
这是一个联合体类型,根据不同的操作命令,传递的值表示不同的意义。
可以通过 semop( )函数来操作信号量,其原型如下:
Exp: 测试 信号量到代码,
代码执行后生成的test文件内容如下:
如果将信号量的加锁和解锁取消, 代码如下:
生成的test文件内容如下:
可以发现两个进程同时访问一个文件,而没有互斥机制的话,就会出现乱码。因此在访问临界资源的时候,就需要
采用互斥机制。
4、共享内存
进程都具有自己的虚拟地址空间(即进程空间),进程A不能随意的访问进程B的进程空间; 内核提供了一种机制,
可以在物理内存中开辟一块存储空间,这块存储空间可供进程A或者进程B访问, 这样一块存储空间就是共享内存。
要使用共享内存也需要获取一个IPC键值,通过 ftok()获取IPC键值。
获取到IPC键值后,就需要向系统申请共享的存储空间,通过函数 shmget( ) 申请共享空间,并获取关于共享内存的
ID标识符。 shmget()的原型如下所示:
申请成功后,还不能访问共享内存,因为访问内存需要知道内存的地址或者指针,所以就需要向系统申请返回
共享内存的地址或者指针。通过 shmmat( )向系统申请返回共享内存的首地址或者指针。其原型如下:
返回值:
成功返回共享内存的首地址(虚拟地址),失败返回NULL。
在成功返回共享内存首地址后,就可以向访问用malloc 分配的内存一样进行操作。
Exp: 申请共享内存,并往共享内存写的文件 shm-w.c
程序执行的效果如下:
【Linux草鞋应用编程系列】_3_进程间通信
本系列文章未完,待续。
如果查看的过程中发现错误,请不吝指教,包括错别字、标点符号等。
前篇:【linux草鞋应用编程系列】_2_ 环境变量和进程控制
linux下面提供了多种进程间通信的方法, 管道、信号、信号量、消息队列、共享内存、套接字等。下面我们分别
介绍管道、信号量、消息队列、共享内存。
信号和套接字在后续介绍。
1、管道
管道又分为无名管道、命名管道。 无名管道用于父子进程间通信, 而命名管道则可以用于同一计算机上运行的
两个进程间的通信。管道可以用类似“水管”的原理来理解。
1)无名管道
要在父子进程进程间使用管道进行通信,那么先需要创建管道, 在linux中使用 pipe()创建管道。其原型如下:
PIPE(2) Linux Programmer’s Manual PIPE(2) NAME pipe - create pipe SYNOPSIS #include <unistd.h> int pipe(int filedes[2]); //参数为一个长度为2 的整型数组的数组首地址, 为输出参数,
返回值:
成功创建管道返回0; 失败则返回-1;
管道分为两端,一端用来写,另一端则用来读(可以想象为水管,位置高的一端进水,位置低的一端将水流出去) 。
pipe()函数的输出参数 filedes[0] 用于读取数据, filedes[1] 用于写入数据。
管道的操作和普通文件的操作一样, 但是要注意,读的时候要将写端关闭,写的时候要将读端关闭。
Exp: pipe.c 首先测试从父进程给子进程写数据。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc,char* argv[]) { int fd_pipe[2]; pid_t pid; char buf[32]; //创建管道 if( pipe(fd_pipe) ) { perror("create pipe"); exit(1); } pid=fork(); if( 0==pid ) { /*close(fd_pipe[1]); //关闭写端*/ read(fd_pipe[0], buf,sizeof(buf)); printf("in child process read data from pipe.\n"); printf("the data read from pipe is:%s\n",buf); exit(0); } /*close(fd_pipe[0]); //关闭读端*/ write(fd_pipe[1], "pipe test",sizeof("pipe test")); sleep(1); return 0; }
程序执行情况如下:
[root@localhost ipc]# gcc main.c [root@localhost ipc]# ./a.out in child process read data from pipe. the data read from pipe is:pipe test
利用系统调用 read()、write()进行操作的时候管道默认是阻塞的,如果管道没有数据可读,那么read( )函数就
阻塞,直到有数据读才返回。
Exp: pipe.c 子进程写入数据到管道,父进程从管道读取数据
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define CHAR "pipe test from child to parent\n" int main(int argc,char* argv[]) { int fd_pipe[2]; pid_t pid; char buf[32]; //创建管道 if( pipe(fd_pipe) ) { perror("create pipe"); exit(1); } pid=fork(); if( 0==pid ) { close(fd_pipe[0]); write(fd_pipe[1],CHAR,sizeof(CHAR)); exit(0); } close(fd_pipe[1]); read(fd_pipe[0],buf,sizeof(buf)); printf("data from child is: %s",buf); return 0; }
执行结果如下:
[root@localhost ipc]# gcc main.c [root@localhost ipc]# ./a.out data from child is: pipe test from child to parent
2)命名管道
命名管道用于系统中两个进程之间通信;命名管道可用于系统中两个没有亲缘关系的进程进行通信(也可以用于父
子进程间的通信) 。
要使用命名管道,则需要创建命名管道,用函数 mkfifo () 创建命名管道。其原型如下:
MKFIFO(3) Linux Programmer’s Manual MKFIFO(3) NAME mkfifo - make a FIFO special file (a named pipe) SYNOPSIS #include <sys/types.h> #include <sys/stat.h> int mkfifo( const char *pathname, //生成的管道特殊文件的位置和文件名 mode_t mode); //管道特殊文件的访问权限
返回值:
成功创建管道文件返回0, 失败返回 -1.
创建号管道特殊文件后,就可以和访问普通文件一样访问管道特殊文件。
Exp: 测试命名管道 pipe-w.c 创建命令管道并向管道写入数据
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <sys/stat.h> #define CHAR "pipe named\n" int main(int argc,char* argv[]) { int fd; int ret; //创建管道 ret=mkfifo("./fifo-pipe",0666); if(ret) { perror("mkfifo: fifo-pipe"); exit(0); } fd=open("./fifo-pipe",O_WRONLY); write(fd,CHAR,sizeof(CHAR)); close(fd); return 0; }
pipe-r.c 打开pipe-w.c 文件,并且从命名管道读取数据:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> int main(int argc,char* argv[]) { int fd; int ret; char buf[32]; //打开命名管道文件 fd=open("./fifo-pipe",O_RDONLY); if(-1 == fd) { perror("open fifo-pipe"); exit(1); } ret=read(fd,buf,sizeof(buf)); if(ret<0) { perror("read fifo-pipe"); exit(1); } printf("the data read from fifo pipe:%s\n",buf); close(fd); return 0; }
程序的执行结果如下:
pipe-w.c 生成wp , pipe-r.c 生成rp
[root@localhost pipe]# ll //查看没有 fifo-pipe 的命名管道文件 总计 28 -rw-r--r-- 1 root root 560 12-11 16:57 pipe_fork.c -rw-r--r-- 1 root root 500 12-11 17:15 pipe-r.c -rw-r--r-- 1 root root 405 12-11 17:17 pipe-w.c -rwxr-xr-x 1 root root 5359 12-11 17:15 rp -rwxr-xr-x 1 root root 5296 12-11 17:17 wp [root@localhost pipe]# ./wp & //wp运行,并且进入后台 [1] 29409 [root@localhost pipe]# jobs [1]+ Running ./wp & //wp在后台运行, 等待命名管道的数据被读取, 即wp 阻塞 [root@localhost pipe]# ./rp //rp 读取管道数据, the data read from fifo pipe:pipe named //数据读取成功 [1]+ Done ./wp //管道中的数据被读取完后,wp不再阻塞,返回 [root@localhost pipe]# jobs [root@localhost pipe]# ll 总计 28 prw-r--r-- 1 root root 0 12-11 17:23 fifo-pipe //生成一个命名管道文件 -rw-r--r-- 1 root root 560 12-11 16:57 pipe_fork.c -rw-r--r-- 1 root root 500 12-11 17:15 pipe-r.c -rw-r--r-- 1 root root 405 12-11 17:17 pipe-w.c -rwxr-xr-x 1 root root 5359 12-11 17:15 rp -rwxr-xr-x 1 root root 5296 12-11 17:17 wp [root@localhost pipe]#
要点:
在进程操作管道的时候, write 和 read 都是阻塞的; 如果写的数据没有被读取走,那么就会写的进程就会
在 write 函数阻塞; 如果读数据的时候,管道没有数据,那么就会等待管道里面别写入数据,进程在read 函数阻塞。
2、消息队列
消息队列也是linux下进程间通信的一种方式, 如果要使用消息队列在进程间进行通信,必须创建一个消息队列
或者打开一个已经存在的消息队列。
要打开一个已经存在的消息队列,或者创建一个新的消息队列,则必须先获取一个关于消息队列的IPC键值;通过函
数 ftok( )获取消息队列的IPC键值。
ftok 的原型如下:
FTOK(3) Linux Programmer’s Manual FTOK(3) NAME ftok - convert a pathname and a project identifier to a System V IPC key //由一个特定的工程号和文件生成一个特定的IPC键值, SYNOPSIS # include <sys/types.h> # include <sys/ipc.h> key_t ftok(const char *pathname, //文件名 int proj_id); //工程号
返回值:
成功返回 IPC键值, 失败返回-1.
要点:
如果文件名和工程号一致,内核保证在任何进程中都将得到同样的 IPC 键值。
有了消息队列的IPC键值后,就是创建或者打开消息队列, 通过 msgget( ) 创建或打开一个消息队列, 其原型如下:
MSGGET(2) Linux Programmer’s Manual MSGGET(2) NAME msgget - get a message queue identifier SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget( key_t key, //IPC 键值 int msgflg); //打开或者创建标志, 可以取值 IPC_CREAT
返回值:
如果成功,返回消息队列的ID号, 失败返回 -1。
创建了消息队列或者打开已经消息队列后,就需要完消息队列中添加消息,即发送消息; 发送消息通过函数 msgsnd( )
实现。
当发送完消息后,就可以从消息队列中获取消息,从消息队列中读取消息用函数 msgrcv( )实现。
原型如下:
MSGOP(2) Linux Programmer’s Manual MSGOP(2) NAME msgop - message operations SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, //消息队列ID const void *msgp, //要发送的消息的消息结构体 size_t msgsz, //消息字符串的大小,或者消息结构的大小 int msgflg); // 消息标志 ssize_t msgrcv(int msqid, //消息队列ID void *msgp, //接受消息的消息结构体指针 size_t msgsz, //消息结构体的大小 long msgtyp, //指定要接收到消息的类型 int msgflg); //消息标志 要发送或接收消息,还需要定义一个如下格式的结构体: struct msgbuf { long mtype; /* message type, must be > 0 */ //消息类型, 这个值必须大于 0 char mtext[1]; /* message data */ //要发送的消息数据, 字符数组长度可以根据实际需要定义 };
消息队列使用完后,需要删除消息队列,通过 msgctl 函数实现,这是一个与ioctl 函数类似的函数,其原型如下:
MSGCTL(2) Linux Programmer’s Manual MSGCTL(2) NAME msgctl - message control operations SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, //消息队列ID int cmd, //操作命令, 操作命令有很多,删除消息队列用 IPC_RMID 命令 struct msqid_ds *buf); //输出参数,通过这个结构体可获取消息队列的状态信息,如果不需要获取 //消息队列的信息,那么就设置为NULL
返回值:
成功删除(cmd=IPC_RMID)返回0 ,失败返回-1.
Exp: 发送消息的源文件: msgsnd.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> typedef struct { long type; char data[128]; }msgbuf; int main(void) { int ret; key_t key; int msgid; msgbuf msg={ type: 1, data: "this is a message queue test.\n", }; //获取键值 key=ftok("./msgsnd.c",1); if(-1 == key) { perror("ftok"); exit(1); } //打开或创建一个消息队列 msgid=msgget(key,IPC_CREAT); if(-1 == msgid ) { perror("msgget"); exit(2); } //发送消息 ret=msgsnd(msgid,&msg,sizeof(msgbuf),0); if(-1 == ret) { perror("msgsnd"); } return 0; }
接收消息的源代码文件: msgrcv.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> typedef struct { long type; char data[128]; }msgbuf; int main(int argc,char* argv[]) { int ret; key_t key; int msgid; msgbuf msg; //获取键值 key=ftok("./msgsnd.c",1); if(-1 == key ) { perror("ftok"); exit(1); } //打开消息队列 msgid=msgget(key,0); if(-1 == msgid ) { perror("msgget"); exit(2); } //接收消息 ret=msgrcv(msgid, &msg, sizeof(msgbuf),1,0); if(-1 == ret) { perror("msgrcv"); exit(3); } printf("the recive message is: %s",msg.data); //删除消息队列 msgctl(msgid,IPC_RMID,NULL); return 0; }
测试结果如下:
[root@localhost msg]# gcc msgsnd.c -o snd [root@localhost msg]# gcc msgrcv.c -o rcv [root@localhost msg]# ./snd [root@localhost msg]# ./rcv the recive message is: this is a message queue test. [root@localhost msg]#
3、信号量
信号量主要用于两个进程间同步的,一般用于多进程间的同步操作。
例如当两个进程同时要访问声卡的时候,那么就需要控制声卡先由那个进程操作,等第一个进程操作完后,其他进程
才能进行操作; 但是为什么我们可以同时用mplayer 还能同时使用kmplyer播放音乐呢? 从用户的角度来看,确实是这
样的,但是从硬件的角度来看,在某一时刻声卡就只能为一个应用层程序服务,当两个应用程序同时对声卡进行操作时就
会出现异常,为了防止这种异常,需要进行对两个进程进行控制,当有一个进程获取声卡的控制权后,另外的一个进程就
不能在同一时刻访问声卡,这就是互斥操作。(可以这样测试, 在windows的操作系统上安装VM虚拟机,打开windows
media player, 然后启动虚拟机(设置虚拟机在启动的时候自动挂载声卡设备),这时候声卡就会工作不正常,会出现
一小段时间的异常, 声音不正常,这就是出现两个应用程序同时使用声卡出现的异常)。
信号量就是为解决类似的问题而设计的,信号量用来控制应用程序在同一时刻对某一系统资源的访问(这个系统资源
也称作临界资源,访问临界资源的代码,也称作临界区代码)。
信号量用结构体 struct sembuf 描述,其定义如下:
/* semop system calls takes an array of these. */ struct sembuf { unsigned short sem_num; /* semaphore index in array */ //信号量集合中的信号量索引值,即表示信号量集合中第几个信号量 short sem_op; /* semaphore operation */ //要对信号量进行的操作,=-1 表示信号量不可获取, =1 表示可以获取信号量 short sem_flg; /* operation flags */ //信号量标志 };
这里有一点需要说明: 通常对信号量进行操作分为 P操作、V操作,当设置 sem_op = -1 时表示进行P操作,
sem_op = 1 时表示要进行V操作。
和消息队列一样,要使用信号量,首先需要获取一个用于信号量到IPC键值, 用 ftok( ) 函数获取。 获取到用于
信号量到IPC键值后,还需要创建或者打开一个已经存在的信号量,通过打开或创建信号量获取一个关于信号量的
信号量ID;然后通过对信号量ID进行操作,就可以使用信号量。
通过semget()函数创建或打开一个信号量,并获取关于信号量的ID; 原型如下:
SEMGET(2) Linux Programmer’s Manual SEMGET(2) NAME semget - get a semaphore set identifier //获取一个信号集合的ID SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget( key_t key, //IPC键值 int nsems, //信号量集合中信号量的个数, 要创建的信号量到个数 int semflg); //信号量的标志, 同OPEN的打开标志类似
在获取信号量集合ID后,需要对信号量进行一些设定(或者说信号量初始化),然后才能操作,通过函数 semctl( )对
信号量进行初始化操作; setctl( )的原型如下:
SEMCTL(2) Linux Programmer’s Manual SEMCTL(2) NAME semctl - semaphore control operations SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, //信号量集合ID int semnum, //信号量集合中的信号量索引值 int cmd, //要对信号量进行的操作,可以使用的命名: IPC_SET、IPC_STAT、IPC_INFO、GETVAL、SETVAL...... ...); //最后一个参数根据 操作的不同,可以传递,也可以不传递
返回值:
如果成功返回0 ,失败返回-1.(操作为 IPC_GETVAL, 成功返回信号量到值 )。
SETVAL: 对信号量进行设置。 这时候,要传递4个参数, 最后一个可变参数,要定义一个类型如下:
union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux specific) */ };
这是一个联合体类型,根据不同的操作命令,传递的值表示不同的意义。
可以通过 semop( )函数来操作信号量,其原型如下:
SEMOP(2) Linux Programmer’s Manual SEMOP(2) NAME semop, semtimedop - semaphore operations SYNOPSIS #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, //信号量集合ID struct sembuf *sops, //信号量结构体指针 unsigned nsops); //表示要操作的信号量个数 int semtimedop(int semid, //信号量集合ID struct sembuf *sops, //信号量结构体指针 unsigned nsops, //表示要操作的信号量个数 struct timespec *timeout); //表示超时等待时间,如果在超时时间内没有获取到可操作的信号量,就返回
Exp: 测试 信号量到代码,
#include <stdio.h> #include <sys/ipc.h> #include <sys/sem.h> #include <fcntl.h> #include <stdlib.h> int main(void) { int i; int j; int ret; int fd; pid_t pid; key_t key; int semid; char buf[64]; int size; struct sembuf sembuf; /*sembuf=(struct sembuf*)malloc(sizeof (struct sembuf));*/ //打开文件,用来进行操作 fd=open("./test",O_RDWR | O_CREAT | O_TRUNC,0666); if(-1 == fd) { perror("open"); exit(1); } //IPC键值 key=ftok("./main.c",1); if(-1 == key) { perror("ftok"); exit(2); } //获取信号量集合的ID semid=semget(key,1,IPC_CREAT); if(-1 == semid ) { perror("semget"); exit(3); } //初始化信号量集合中的第一个信号量,设定信号量的值为0 , sem.sem_op = 0; ret=semctl(semid, 0, SETVAL, 1); pid=fork(); if( pid==0 ) //----------------子进程------------ { //信号量的P 操作, 即加锁信号量 sembuf.sem_num=0; sembuf.sem_op=-1; sembuf.sem_flg=0; semop(semid,&sembuf,1); size=sprintf(buf,"pid=%d, ppid=%d\n",getpid(),getppid()); for(i=0;i<5;i++) { j=0; while(j<size) { ret=write(fd,&buf[j++], 1); if(-1 == ret) { perror("write"); exit(4); } usleep(1); } } //信号量的V操作,即解锁信号量 sembuf.sem_num=0; sembuf.sem_op=1; sembuf.sem_flg=0; semop(semid,&sembuf,1); exit(0); }//---------------子进程结束--------------------- //-----------------------父进程--------------- //信号量的P 操作, 即加锁信号量 sembuf.sem_num=0; sembuf.sem_op=-1; sembuf.sem_flg=0; semop(semid,&sembuf,1); size=sprintf(buf,"pid=%d, ppid=%d\n",getpid(),getppid()); for(i=0;i<5;i++) { j=0; while(j<size) { ret=write(fd,&buf[j++], 1); if(-1 == ret) { perror("write"); exit(4); } usleep(1); } } //信号量的V操作,即解锁信号量 sembuf.sem_num=0; sembuf.sem_op=1; sembuf.sem_flg=0; semop(semid,&sembuf,1); semctl(semid,0,IPC_RMID); close(fd); return 0; }
代码执行后生成的test文件内容如下:
pid=1114, ppid=714 pid=1114, ppid=714 pid=1114, ppid=714 pid=1114, ppid=714 pid=1114, ppid=714 pid=1115, ppid=1 pid=1115, ppid=1 pid=1115, ppid=1 pid=1115, ppid=1 pid=1115, ppid=1
如果将信号量的加锁和解锁取消, 代码如下:
#include <stdio.h> #include <sys/ipc.h> #include <sys/sem.h> #include <fcntl.h> #include <stdlib.h> int main(void) { int i; int j; int ret; int fd; pid_t pid; key_t key; int semid; char buf[64]; int size; struct sembuf sembuf; /*sembuf=(struct sembuf*)malloc(sizeof (struct sembuf));*/ //打开文件,用来进行操作 fd=open("./test",O_RDWR | O_CREAT | O_TRUNC,0666); if(-1 == fd) { perror("open"); exit(1); } //IPC键值 key=ftok("./main.c",1); if(-1 == key) { perror("ftok"); exit(2); } //获取信号量集合的ID semid=semget(key,1,IPC_CREAT); if(-1 == semid ) { perror("semget"); exit(3); } //初始化信号量集合中的第一个信号量,设定信号量的值为0 ret=semctl(semid, 0, SETVAL, 1); pid=fork(); if( pid==0 ) //----------------子进程------------ { //信号量的P 操作, 即加锁信号量 sembuf.sem_num=0; sembuf.sem_op=-1; sembuf.sem_flg=0; /*semop(semid,&sembuf,1);*/ //取消信号量到作用 size=sprintf(buf,"pid=%d, ppid=%d\n",getpid(),getppid()); for(i=0;i<5;i++) { j=0; while(j<size) { ret=write(fd,&buf[j++], 1); if(-1 == ret) { perror("write"); exit(4); } usleep(1); } } //信号量的V操作,即解锁信号量 sembuf.sem_num=0; sembuf.sem_op=1; sembuf.sem_flg=0; /*semop(semid,&sembuf,1);*/ //取消信号量到作用 exit(0); }//---------------子进程结束--------------------- //-----------------------父进程--------------- //信号量的P 操作, 即加锁信号量 sembuf.sem_num=0; sembuf.sem_op=-1; sembuf.sem_flg=0; /*semop(semid,&sembuf,1);*/ //取消信号量到作用 size=sprintf(buf,"pid=%d, ppid=%d\n",getpid(),getppid()); for(i=0;i<5;i++) { j=0; while(j<size) { ret=write(fd,&buf[j++], 1); if(-1 == ret) { perror("write"); exit(4); } usleep(1); } } //信号量的V操作,即解锁信号量 sembuf.sem_num=0; sembuf.sem_op=1; sembuf.sem_flg=0; /*semop(semid,&sembuf,1);*/ //取消信号量到作用 semctl(semid,0,IPC_RMID); close(fd); return 0; }
生成的test文件内容如下:
ppiidd==22333387,, ppppiidd==2731347 ppiidd==22333378,, ppppiidd==721343 7p ipdi=d2=323373,8 ,p ppipdi=d7=1243 3p7i dp=i2d3=3273,3 8p,p ipdp=i7d1=42 3p3i7d =p2i3d3=72,3 3p8p,i dp=p7i1d4= 2337
可以发现两个进程同时访问一个文件,而没有互斥机制的话,就会出现乱码。因此在访问临界资源的时候,就需要
采用互斥机制。
4、共享内存
进程都具有自己的虚拟地址空间(即进程空间),进程A不能随意的访问进程B的进程空间; 内核提供了一种机制,
可以在物理内存中开辟一块存储空间,这块存储空间可供进程A或者进程B访问, 这样一块存储空间就是共享内存。
要使用共享内存也需要获取一个IPC键值,通过 ftok()获取IPC键值。
获取到IPC键值后,就需要向系统申请共享的存储空间,通过函数 shmget( ) 申请共享空间,并获取关于共享内存的
ID标识符。 shmget()的原型如下所示:
SHMGET(2) Linux Programmer’s Manual SHMGET(2) NAME shmget - allocates a shared memory segment SYNOPSIS #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, //IPC 键值 size_t size, //要申请的内存空间的大小 int shmflg); //共享内存的空间打开标志 ,与 open 的打开标志类似, 返回值: 申请成功返回共享内存标识ID, 失败返回-1。
申请成功后,还不能访问共享内存,因为访问内存需要知道内存的地址或者指针,所以就需要向系统申请返回
共享内存的地址或者指针。通过 shmmat( )向系统申请返回共享内存的首地址或者指针。其原型如下:
SHMOP(2) Linux Programmer’s Manual SHMOP(2) NAME shmop - shared memory operations SYNOPSIS #include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, //共享内存标志ID const void *shmaddr, //传递NULL,表示要系统分配存储缓冲区,传递地址表示指定地址 int shmflg); //打开标志, int shmdt(const void *shmaddr); //删除共享内存
返回值:
成功返回共享内存的首地址(虚拟地址),失败返回NULL。
在成功返回共享内存首地址后,就可以向访问用malloc 分配的内存一样进行操作。
Exp: 申请共享内存,并往共享内存写的文件 shm-w.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <string.h> #define SHM_SIZE 128 int main(int argc,char* argv[]) { key_t key; int shm_id; char* shm_p; //获取IPC 键值 key=ftok("./shm-w.c",1); if(-1 == key) { perror("ftok"); exit(1); } //申请共享内存空间,大小为 SHM_SIZE shm_id=shmget(key,SHM_SIZE,IPC_CREAT); if(-1 == shm_id ) { perror("shmget"); exit(2); } //将申请的共享内存映射到用户空间 shm_p=shmat(shm_id,NULL,0); // if(NULL == shm_p ) { perror("shmat"); exit(3); } //将数据写入到共享内存 写入到数据可以在其他进程中读取 memset(shm_p,0,SHM_SIZE); strcpy(shm_p, "this is a sheard memmory.\n"); //这个函数不安全,需要注意 return 0; } 从共享内存中读取数据的文件 shm-r.c #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <string.h> #define SHM_SIZE 128 int main(int argc,char* argv[]) { key_t key; int shm_id; char* shm_p; char buf[SHM_SIZE]; //获取IPC 键值 key=ftok("./shm-w.c",1); if(-1 == key) { perror("ftok"); exit(1); } //申请共享内存空间,大小为 SHM_SIZE shm_id=shmget(key,SHM_SIZE,IPC_CREAT); if(-1 == shm_id ) { perror("shmget"); exit(2); } //将申请的共享内存映射到用户空间 shm_p=shmat(shm_id,NULL,0); // if(NULL == shm_p ) { perror("shmat"); exit(3); } //从共享内存读取数据 memset(buf,0,SHM_SIZE); strcpy(buf, shm_p); //这个函数不安全,需要注意 printf("the data read from sheard memory is: %s",buf); shmdt(shm_p); //申请撤销共享内存 return 0; }
程序执行的效果如下:
[root@localhost shm]# gcc shm-w.c -o shmw [root@localhost shm]# gcc shm-r.c -o shmr [root@localhost shm]# ./shmw [root@localhost shm]# ./shmr the data read from sheard memory is: this is a sheard memmory. [root@localhost shm]#
【Linux草鞋应用编程系列】_3_进程间通信
本系列文章未完,待续。
如果查看的过程中发现错误,请不吝指教,包括错别字、标点符号等。
前篇:【linux草鞋应用编程系列】_2_ 环境变量和进程控制
相关文章推荐
- 【linux草鞋应用编程系列】_1_ 开篇_系统调用IO接口与标准IO接口
- 【linux草鞋应用编程系列】_6_ 重定向和VT100编程
- 【linux草鞋应用编程系列】_4_ 应用程序多线程
- 【linux草鞋应用编程系列】_2_ 环境变量和进程控制
- 【linux草鞋应用编程系列】_5_ Linux网络编程
- Linux 系统应用编程——进程间通信(下)
- linux高级编程基础系列:System V进程间通信(信号量、共享内存)
- linux下进程间通信系列(四、socket编程)转
- Linux 系统应用编程——进程间通信(上)
- Linux应用编程基础--(9)进程间通信
- Linux 系统应用编程——进程间通信(下)
- Linux应用编程基础--(10)进程间通信system V
- Linux 系统应用编程——进程间通信(上)
- 嵌入式成长轨迹20 【Linux应用编程强化】【Linux下的C编程 下】【进程间通信】
- 介绍linux及编程的一本好书(Linux操作系统应用及编程)
- 【Linux系统编程应用】 Linux输入子系统(二)
- 浅析 Linux 中的时间编程和实现原理,第 1 部分: Linux 应用层的时间编程
- linux下mege88单片机的IAP在应用编程
- linux网络设备应用与驱动编程学习4——模板与实例(B)——打开和释放方法
- “手把手教你学linux驱动开发”OK6410系列之01---模块编程