linux c编程:进程间通讯

进程间的通讯包括管道,共享内存,信号量通讯,消息队列,套借口(socket)和全双工管道通讯linux

首先来看下管道的用法:管道顾名思义,就如同下水道管道同样,当从管道一端流水到另外一端的时候,水流的方向是单方向的。某一时刻只能从单方向传递数据,不能双向传递。这种就叫单双工模式。半双工模式只能是一端写数据,一端读数据。来看一个半双工的例子:shell

(1)在父进程中经过pipe()函数建立一个管道。产生一个描述符,fd[0]指向管道的读端,fd[1]指向管道的写端ubuntu

(2) 在父进程中调用fork函数产生一个子进程。子进程和父进程都分别指向fd的读端和写端数组

(3) 在子进程中,关闭fd[0]也就是读的端口,往fd[1]写端口写入数据,在父进程中关闭写端口fd[1],从读端口fd[0]中读取数据并显示在终端上。在父进程中调用了sleep(2)等待2秒的函数,目的是等待子进程写数据数据结构

int pipe_function()
{
    int fd[2],pid,line;
    char message[100];
    if (pipe(fd) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        wait(NULL);
        exit(0);
    }
    return 0;
}socket

运行结果:函数

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3spa

Child process send message操作系统

Parent process receive message is:指针

Welcome to microsof


上面的例子演示了单向通讯,若是咱们想获得双向通讯,父进程在读的同时也给子进程写。要实现这样的功能,咱们就必须创建2个管道,一个管道分别是从父进程流向子进程,一个管道是从子进程流向父进程。代码实现以下:定义了两个管道fdfd1

int pipe_function_multiy()
{
    int fd[2],pid,line,fd1[2],line1;
    char message[100],message1[100];
    if (pipe(fd) == -1 || pipe(fd1) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
        sleep(2);
        close(fd1[1]);
        printf("Child process receive message is:\n");
        line1=read(fd1[0],message1,100);
        write(STDOUT_FILENO,message1,line1);
        printf("\n");
        exit(0);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        close(fd1[0]);
        printf("parent process send message is:\n");
        write(fd1[1],"hello how are you!",20);
        exit(0);
    }
    return 0;
}

执行结果:

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

Parent process receive message is:

Child process send message

Welcome to microsof

parent process send message is:

Child process receive message is:

hello how are you!

 

 

 

在上面的例子中,管道只能在有关联的进程中进行,也就是父子进程。那若是不相关的两个进程也须要进行通讯该如何解决呢。这里就要到命令管道。一般称为FIFO。经过这个名称能够知道命令管道遵循先进先出的原则。建立一个命令管道有两种方法一种是经过函数建立命名管道,一种是经过shell命令来建立。

 

首先来看下经过shell命令建立:

 

()首先经过mkfifo建立一个管道文件test

 

root@zhf-linux:/home/zhf/zhf# mkfifo test

 

()在一个终端中经过cat命令来查看这个命令管道中的数据。因为没有任何写入的数据,所以一直处于等待。这个时候cat命令将一直挂起,直到终端或者有数据发送到FIFO中。

 

root@zhf-linux:/home/zhf/zhf# cat ./test

 

()而后打开另一个终端,向FIFO中写入数据

 

root@zhf-linux:/home/zhf/zhf# echo "hello fifo" > ./test

 

()这个时候cat命令将会输出内容

 

root@zhf-linux:/home/zhf/zhf# cat ./test

 

hello fifo

 


 


 

那接下来咱们看下经过C代码的方式来实现命令管道的方法:

 

首先建立2个文件。表明2个进程,一个读,一个写。仍是用刚才建立的test这个命令管道文件

 

test3.c

int mkfifo_function_a()
{
    int fd;
    int pid;
    fd=open(FIFO,O_RDWR);
    printf("write the message:\n");
    write(fd,"hello world",20);
    close(fd);
    
}

test4.c

int mkfifo_function_b()
{
    char msg[100];
    int fd;
    int pid;
    int line;
    fd=open(FIFO,O_RDWR);
    printf("read the message:\n");
    line=read(fd,msg,100);
    close(fd);
    write(STDOUT_FILENO,msg,line);
}

 

 

flags=O_RDONLYopen将会调用阻塞,除非有另一个进程以写的方式打开同一个FIFO,不然一直等待。

 

flags=O_WRONLYopen将会调用阻塞,除非有另一个进程以读的方式打开同一个FIFO,不然一直等待。

 

flags=O_RDONLY|O_NONBLOCK:若是此时没有其余进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。

 

flags=O_WRONLY|O_NONBLOCK:当即返回,若是此时没有其余进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1

 



 

()在一个终端中运行test4.c。显示读取数据。因为没有输入数据,所以一直挂起

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test4

 

read the message:

 

() 在另一个终端中运行test3.c。写入数据

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

 

write the message:

 

()此时显示出读取信息:

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test4

 

read the message:

 

hello world

 

 

共享内存:

 

前面介绍到父子进程分别是访问不一样的内存,由于子进程拷贝了另一分内存地址。那么若是想两个进程访问同一块内存地址的数据,就须要用到共享内存了。先来看几个共享内存的函数:

 

shmget: 建立一块共享内存区域。若是已存在这一块的共享内存。该函数能够打开这个已经存在的共享内存。原型为:

 

int shmget(key_t key, size_t size, int shmflg);

 

第一个参数,与信号量的semget函数同样,程序须要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

 

不相关的进程能够经过该函数的返回值访问同一共享内存,它表明程序可能要使用的某个资源,程序对全部共享内存的访问都是间接的,程序先经过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,全部其余的信号量函数使用由semget函数返回的信号量标识符。

 

第二个参数,size以字节为单位指定须要共享的内存容量

 

第三个参数,shmflg是权限标志,它的做用与open函数的mode参数同样,若是要想在key标识的共享内存不存在时,建立它的话,能够与IPC_CREAT作或操做。共享内存的权限标志与文件的读写权限同样,举例来讲,0644,它表示容许一个进程建立的共享内存被内存建立者所拥有的进程向共享内存读取和写入数据,同时其余用户建立的进程只能读取共享内存。

 

shmat函数:

 

shmat()函数的做用就是用来启动对该共享内存的访问,并把共享内存链接到当前进程的地址空间。它的原型以下:

 

void *shmat(int shm_id, const void *shm_addr, int shmflg);

 

第一个参数,shm_id是由shmget()函数返回的共享内存标识。

 

第二个参数,shm_addr指定共享内存链接到当前进程中的地址位置,一般为空,表示让系统来选择共享内存的地址。

 

第三个参数,shm_flg是一组标志位,一般为0

 

调用成功时返回一个指向共享内存第一个字节的指针,若是调用失败返回-1.

 

shmdt函数:

 

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并非删除它,只是使该共享内存对当前进程再也不可用。它的原型以下:

 

int shmdt(const void *shmaddr);

 

参数 shmaddrshmat()函数返回的地址指针,调用成功时返回 0,失败时返回 -1.

 

shmctl函数:用来控制共享内存

 

int shmctl(int shm_id, int command, struct shmid_ds *buf);

 

第一个参数,shm_idshmget()函数返回的共享内存标识符。

 

第二个参数,command是要采起的操做,它能够取下面的三个值 :

 

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

  • IPC_SET:若是进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

  • IPC_RMID:删除共享内存段

 

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

 

下面来看下一个简单的父子进程访问共享内存的方法:
int share_memory_function()
{
    int shmid;
    int proj_id;
    key_t key;
    int sizz;
    char *addr;
    pid_t pid;
    key=IPC_PRIVATE;
    shmid=shmget(key,1024,IPC_CREAT|0660);
    if (shmid == -1)
    {
        perror("create share memory failed!");
        return 1;
    }
    addr=(char *)shmat(shmid,NULL,0);
    if(addr == (char *)(-1))
    {
        perror("can not attach");
        return 1;
    }
    strcpy(addr,"welcome to ubuntun");
    pid=fork();
    if (pid == -1)
    {
        perror("error!");
        return 1;
    }
    if (pid == 0)
    {
        printf("Child process string is %s\n",addr);
        exit(0);
    }
    else
    {
        wait(NULL);
        printf("Parent process string is %s\n",addr);
        if (shmdt(addr) == 1)
        {
            perror("release failed");
            return 1;
        }
        if (shmctl(shmid,IPC_RMID,NULL)==-1)
        {
            perror("failed");
            return 1;
        }
    }
    return 0;

}
运行结果:
root@zhf-linux:/home/zhf/zhf/c_prj# ./test4
Child process string is welcome to ubuntun
Parent process string is welcome to ubuntun
在共享内存中,只要获取到了共享内存的地址,如何进程均可以操做内存,这样就会致使一个问题,多个进程对内存中的变量进行读写。从而使得变量的值变得不可控。信号量就能够解决这个问题。当有一个进程要求使用某一个共享内存中的资源时,系统会首先判断该资源的信号量,若是大于 0,则可使用该资源,而且信号量要减一,当再也不使用该资源的时候,信号量再加一。若是信号量等于 0,就进入了休眠状态。资源不可访问。
来看下信号量的使用:

1、建立信号量

semget函数建立一个信号量集或访问一个已存在的信号量集。

#include <sys/sem.h>

int semget (key_t key, int nsem, int oflag) ;

返回值是一个称为信号量标识符的整数,semopsemctl函数将使用它。

参数nsem指定集合中的信号量数。(若用于访问一个已存在的集合,那就能够把该参数指定为0

参数oflag能够是SEM_R(read)SEM_A(alter)常值的组合。(打开时用到),也能够是IPC_CREATIPC_EXCL ;

 

2、打开信号量

使用semget打开一个信号量集后,对其中一个或多个信号量的操做就使用semop(op--operate)函数来执行。

#include <sys/sem.h>

int semop (int semid, struct sembuf * opsptr, size_t nops) ;

参数opsptr是一个指针,它指向一个信号量操做数组,信号量操做由sembuf结构表示:

 

struct sembuf{

short sem_num; // 除非使用一组信号量,不然它为0

short sem_op; // 信号量在一次操做中须要改变的数据,一般是两个数,

// 一个是-1,即P(等待)操做,一个是+1,即V(发送信号)操做

short sem_flg; // 一般为SEM_UNDO,使操做系统跟踪信号,并在进程没有释放该信号量而终止时,

// 操做系统释放信号量

};

◆参数nops规定opsptr数组中元素个数。

sem_op值:

1)若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操做)

2)若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(P操做)

3)若sem_op0,这表示调用进程但愿等待到该信号量值变成0

◆若是信号量值小于sem_op的绝对值(资源不能知足要求),则:

1)若指定了IPC_NOWAIT,则semop()出错返回EAGAIN

2)若未指定IPC_NOWAIT,则信号量的semncnt值加1(由于调用进程将进 入休眠状态),而后调用进程被挂起直至:①此信号量变成大于或等于sem_op的绝对值;②从系统中删除了此信号量,返回EIDRM;③进程捕捉到一个信 号,并从信号处理程序返回,返回EINTR。(与消息队列的阻塞处理方式 很类似)

 3、信号量操做

semctl函数对一个信号量执行各类控制操做。

#include <sys/sem.h>

int semctl (int semid, int semnum, int cmd, /*可选参数*/ ) ;

第四个参数是可选的,取决于第三个参数cmd

参数semnum指定信号集中的哪一个信号(操做对象)

参数cmd指定如下10种命令中的一种,semid指定的信号量集合上执行此命令。

IPC_STAT   读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

IPC_SET     设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。

IPC_RMID  将信号量集从内存中删除。

GETALL      用于读取信号量集中的全部信号量的值。

GETNCNT  返回正在等待资源的进程数目。

GETPID      返回最后一个执行semop操做的进程的PID

GETVAL      返回信号量集中的一个单个的信号量的值。

GETZCNT   返回这在等待彻底空闲的资源的进程数目。

SETALL       设置信号量集中的全部的信号量的值。

SETVAL      设置信号量集中的一个单独的信号量的值。

下面来看个具体的应用。首先在/home/zhf/zhf/c_prj/test1.c上建立一个信号量,在模拟系统分配资源。没隔3秒钟就有一个资源被占用。

Test6.c中的代码:

#define RESOURCE 4

int sem_function_set()
{
    key_t key;
    int semid;
    struct sembuf sbuf={0,-1,IPC_NOWAIT};
    union semun arg;
    if ((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("ftok error!\n");
        exit(1);
    }
    if ((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("segmet error!\n");
        exit(1);
    }
    arg.val=RESOURCE;
    if(semctl(semid,0,SETVAL,arg)==-1)
    {
        perror("semctl errror!\n");
        exit(1);
    }
    while(1)
    {
        if (semop(semid,&sbuf,1)==-1)
        {
            perror("semop error!\n");
            exit(1);
        }
        sleep(3);
    }
    semctl(semid,0,IPC_RMID,0);
    exit(0);
}

test5.c中的代码:

int sem_get()
{
    key_t key;
    int semid,semval;
    union semun arg;
    if((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("key errror1\n");
        return 1;
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("semget error!\n");
        return 1;
    }
    while(1)
    {
        if((semval=semctl(semid,0,GETVAL,0))==-1)
        {
            perror("semctl error!\n");
            exit(1);
        }
        if(semval > 0)
        {
            printf("%d resource could be used\n",semval);
        }
        else
        {
            printf("no resource could be used\n");
            break;
        }
        sleep(3);
    }
    exit(0);
}

 

分别在2个终端执行:结果以下

相关文章
相关标签/搜索