Linux C进程间通信

一、基本概念

  1.进程组:属于同一个登录shell的进程

  2.内核和超级用户可以发送信号到所有进程

    普通用户:只能向那些有相同UID,GID的进程发送信号,或者是一个进程组的进程发送信号

  3.管道:单向的,先入先出,固定大小的数据流。进程读取空管道时,直到有数据输入前,进程阻塞;管道已满,进程写入时,进程阻塞

   命名管道 FIFOS 通过mkfifo创建

  4.System V中三种进程通信机制:消息队列、信号量、共享内存

   5.临界区是一段代码,任意时刻只能由一个进程之星它

6.进程收到信号的处置方式:1)忽略信号2)执行处理信号的函数3)暂停进程的执行4)重启刚才暂停的进程5)采用系统默认的操作

  7.信号 SIGHUP 挂断控制终端 SIGINT 控制终端中断键按下SIGKILL 删除一个或一组进程,信号不能别忽略  SIGSTOP 暂停进程

8.内核转储:终止进程时没留下一个称为core的文件,存储当时进程内存中的内容,用于事后查错。

 

二.注册信号调用函数

signal()

#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回-1。

 

例如:

void sigcatcher(int signum);

int main()
{
  char buffer1[100],buffer2[100];
  int i;
  f(signal(SIGTERM,&sigcatcher)==-1)
  {
    printf("Couldn't resister signal handleer!\n");
    exit(1);
  }
  printf("pid if this process is :%d \n",getpid());
  printf("Please input:\n");
  for(;;)
  {
  fgets(buffer1,sizeof(buffer1),stdin);
  for(i=0;i<100;i++)
  {
  if(buffer1[i]>=97 && buffer1[i]<=122)
  buffer2[i]=buffer1[i]-32;
  else
  buffer2[i]=buffer1[i];
  }
  printf("Your inout is %s \n",buffer2);
  }
  exit(0);
}

void sigcatcher(int signum)
{
  printf("catch signal SIZETERM.\n");//在终端另一个窗口输入kill 进程号就会结束进程
  exit(0);

  }

 

2.

#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

sigaction结构定义如下:

struct sigaction {

         __sighandler_t _sa_handler;

            void (*_sa_sigaction)(int,struct siginfo *, void *);

            sigset_t sa_mask;

            unsigned long sa_flags;

}

 

例如:

  struct sigaction act;
   act.sa_handler=sighandler;
  sigemptyset(&act.sa_mask);
  act.sa_flags=0;
  if(sigaction(SIGTERM,&act,NULL)==-1)
  {
    printf("Couldn't resister signal handleer!\n");
    exit(1);
  }

实现和signal一样的功能

 

3.信号集

信号集被定义为一种数据类型:

typedef struct {

                       unsigned long sig[_NSIG_WORDS];

} sigset_t

信号集用来描述信号的集合,每个信号占用一位。Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

 

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum)

int sigdelset(sigset_t *set, int signum);

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

sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;

sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;

sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;

sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;

sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

信号集操作

1. int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));  oldset非空返回当前信号掩码;set非空,根据how设定掩码;set为空没有意义 执行成功返回0,否则返回-1

      SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号

     SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞

      SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void pr_mask(int signum)
{
sigset_t sigset;
if(sigprocmask(0,NULL,&sigset)<0)//返回当前进程的信号掩码,到sigset中
{
   printf("sigprocmask error\n");
   exit(0);
}
if(sigismember(&sigset,SIGINT))
      printf("SIGINT\n");
if(sigismember(&sigset,SIGTERM))
{
    printf("SIGTERM\n");
exit(0);
}
}

 

if(signal(SIGINT,&pr_mask)==-1)//pr_mask 收到信号后处理函数
{
    printf("Couldn't resister signal handleer!\n");
    exit(1);
}
if(signal(SIGTERM,&pr_mask)==-1)
{
    printf("Couldn't resister signal handleer!\n");
    exit(1);
}

 

2.sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

 

static void sig_quit(int);

int main()
{
  sigset_t newmask,oldmask,pendmask;
  if(signal(SIGQUIT,&sig_quit)==-1)  //注册信号SIGQUIT
  {
  printf("couldn't register signal handler for SIGQUIT.\n");
  exit(1);
  }
  sigemptyset(&newmask);//清空信号集
  sigaddset(&newmask,SIGQUIT);//添加SIGQUIT信号


  if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)//保存当前信号集
  {
    printf("SIG_BLOCK error.\n");
    exit(2);
  }
  sleep(5);
  if(sigpending(&pendmask)<0)//检测是否有挂起信号
  {
  printf("sigpend error\n");
  exit(3);
  }
  if(sigismember(&pendmask,SIGQUIT))//查看SIGQUIT是否在挂起信号集中
    printf("SIGQUIT pending\n");
  if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)//消除信号的阻塞
  {
    printf("SIG_SETMASK error.\n");
    exit(4);
  }
  printf("SIGQUIT unblocked.\n");
    sleep(5);
    exit(0);
}

static void sig_quit(int signum)
{
    printf("catch SIGQUIT\n");
  if(signal(SIGQUIT,SIG_DFL)==-1)
   printf("Couldn't reset SIGQUIT!\n");
return;
}

 

 

3.sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

while(quitflag==0)

   sigsuspend(&zeromask);   //直到quitflag变为1时,进程苏醒,重新执行

 

 

三.发送信号

1.raise 函数

int raise(int signum) 想一个进程自身发送信号,成功返回0,否则返回-1

2.kill 

int kill(pid_t pid,int signo) 成功返回0 错误返回1

 

该系统调用可以用来向任何进程或进程组发送任何信号。参数pid的值为信号的接收进程

pid>0 进程ID为pid的进程

pid=0 同一个进程组的进程

pid<0 pid!=-1 进程组ID为 -pid的所有进程

pid=-1 除发送进程自身外,所有进程ID大于1的进程

 

四、管道

管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
  A. 管道是半双工的,数据只能向一个方向流动;
  B. 需要双工通信时,需要建立起两个管道;
  C. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  D. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,
     但它不是普通的文件,它不属于某种文件系统,而是自立门户,
     单独构成一种文件系统,并且只存在与内存中。
 
数据的读出和写入:
一个进程向管道中写的内容被管道另一端的进程读出。
写入的内容每次都添加在管道缓冲区的末尾,
并且每次都是从缓冲区的头部读出数据。
 
一、创建匿名管道
 #include <unistd.h>
1.int pipe(int fd[2]); fd[0] 读通道 fd[1]写通道  调用成功返回0否则返回-1
特点:1.没有名字,为了一次使用建立的
      2.两个文件描述符同时打开,向一个没有写进程的向管道写数据的管道读数据read 返回文件结束,向一个没有读进程读数据的管道写数据,返回错误
      3.不允许文件定位,读写都是顺序的

else if(pid>0)
{
  close(fd[0]);
  write(fd[1],"How are you?\n",12);//对管道进行读写操作时,先关闭另外一端
}
else
{
  close(fd[1]);
  n=read(fd[0],buffer,1024);
  write(STDOUT_FILENO,buffer,n);

 }

2.if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)//标准输入设为管道的读数据一端
{
  printf("dup2 error to stdin!\n");
  exit(1);
}
close(fd[0]);

 

 

3.FILE* popen(char *comd,char * type)调用fork 和exec函数执行命令行命令

  if(popen("upcase","w"))==null)

{

  printf("popen error");

exit(1);

}

 pcloese(FILE * fp)

if(pclose(fp)==-1)

{

 printf("pclose error.\n");

 exit(1);

}

二.命名管道

不同之处:1)可用于非相同祖先进程之间通信

             2)内容存储在文件系统中,除非删除,否则不会消失

操作:1)必须打开才能进行读写操作

         2)没有其他进程打开一个命名管道式,就对其进行读操作,会产生SIGPIPE信号;所有写进程都关闭了,读管道认为到达文件末尾

         3)多个进程写操作进行的情况下,写交错的情况会发生。

应用:shell命令行传递命令

        客户服务器之间交换数据 

int  mkfifo(char * pathname,mode_t mode)

int mknod(chat *pathname,mode_t mode,dev_t dev);

服务器程序

读取数据

/**ex6-12client.c**/
#include<stdio.h>
#include<stdlib.h>

#define FIFO_FILE "MYFIFO"

int main(int argc,char *argv[])
{
FILE *fp;
int i;
if(argc<=1)
{
printf("usage:%s<pathname>\n",argv[0]);
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL)
{
printf("open fifo failed.\n");
exit(1);
}
for(i=1;i<argc;i++)
{
if(fputs(argv[i],fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
if(fputs("",fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
}
fclose(fp);
return 0;
}

 

 

 

客户端程序,输入数据

/**ex6-12client.c**/
#include<stdio.h>
#include<stdlib.h>

#define FIFO_FILE "MYFIFO"

int main(int argc,char *argv[])
{
FILE *fp;
int i;
if(argc<=1)
{
printf("usage:%s<pathname>\n",argv[0]);
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL)
{
printf("open fifo failed.\n");
exit(1);
}
for(i=1;i<argc;i++)
{
if(fputs(argv[i],fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
if(fputs("",fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
}
fclose(fp);
return 0;
}

 

三。system V IPC 机制

1.关键字和标识符

标识符:每个对象都和唯一一个引用标识符相连,唯一性局限在同一IPC对象内

关键字:用于定位system v中IPC机制的对象的引用标识符,类型为key_t 包含在头文件<sys/types.h>中

#include<sys/ipc.h>

key_t ftok(const char *path,int id) 返回key_t    pathname 路径名 id只有低8位有效

2.ipc_perm   包含于头文件<sys/ipc.h>中包含了uid,gid,cuid,cgid,mod,seq(应用序号),key关键字

3.ipcs 用于显示IPC机制所有对象的状态

 

一).消息队列

每个消息队列都有一个msqid_ds结构与其关联:

struct msqid_ds
  {
    struct msqid_ds {
    struct ipc_perm msg_perm;  /*队列对应的ipc_perm指针
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

1.创建消息队列

#include<sys/types.h>

#include<sys/ipe.h>

#include<sys/msq.h>

int msgget(key_t key,int flag);

1)key==IPC_PRIVATE  flag 为何值都创建消息队列

2)key!=IPV_PRIVATE flag 置位IPC_CREATE 没设置 IPC_EXCL 则既能打开又能创建;key 与内核存在关键字相同时,打开消息队列,返回引用标志符;否则执行创建,返回标识符

3))key!=IPV_PRIVATE flag 置位IPC_CREATE 置 IPC_EXCL 执行创建,若存在消息队列,则返回错误

flag 设置权限

例如:

if((msqid=msgget(key,IPC_CREAT|0660))==-1)
{
  printf("the message failed.\n");
  exit(1);
}
printf("the message succed :msqid=%d\n",msqid);

2.发送接收消息

1)消息结构

struct mymsg

{

 long mtype;

 char mtext[512];

}

发送

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgsnd(int msqid,const void *ptr,size_t nbytes,int flags);

msqid:消息队列的引用标识符,插入队尾

ptr :指向长整数的指针

nbytes:消息的长度,字节

flag:0或者IPC_NOWAIT   

函数成功返回0否则返回-1

int msgrcv(int msqid,const void *ptr,size_t nbytes,long type,int flags); 

type=0 f返回消息队列中第一个消息;〉返回类型域等于这个值得消息;<0 在类型域<=type绝对值的消息中,类型域最小的第一个消息

flag: IPC——NOWAIT当type 无效直接返回错误,否则阻塞发送消息进程,直至type变为有效

        MSG_NOERROR,当数据长度超过nbytes时,截断数据,正确返回,否则函数错误返回,消息仍然存在队列中

调用成功返回字节数,否则返回-1

接收消息

/**ex6-14**/
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct my_msg
{
long int my_msg_type;
char text[BUFSIZ];
}msgbuf;

int main()
{
int running=1;
int msgid;
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
printf("msgget failed!\n");
exit(1);
}
while(running)
{
printf("Enter somt text:");
fgets(msgbuf.text,BUFSIZ,stdin);
msgbuf.my_msg_type=1;
if(msgsnd(msgid,(void *) &msgbuf,BUFSIZ,0)==-1)
{
printf("msgsnd failed!\n");
exit(1);
}

if(strncmp(msgbuf.text,"end",3)==0)
running=0;

}

//接收信息

int main()
{
int running=1;
int msgid;
long int msg_to_receive=0;
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
printf("msgget failed!\n");
exit(1);
}
while(running)
{
if(msgrcv(msgid,(void *)& msgbuf,BUFSIZ,msg_to_receive,0)==-1)
{
printf("received failed\n");
exit(1);

}
printf("You wrote:%s ",msgbuf.text);
if(strncmp(msgbuf.text,"end",3)==0)
running=0;

}

if(msgctl(msgid,IPC_RMID,0)==-1)
{
printf("msgct(IPC_RMID) failed!\n");
exit(1);

}
return 0;
}

 

3.控制消息

int msgctl(int msqid,int cmd,struct msqid_ds *buf)成功返回0 否则返回-1

cmd:IPC_STAT 复制数据到buf

      IPC_SET 利用buf设置msqid

      IPC_RMID 删除msqid 及数据结构

 

二)信号量

共享资源:互斥共享:同一时刻只允许一个进程对资源进行操作

             同步共享:同一时刻若干资源进行操作

1.信号量结构体
内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集
中的每个信号量对应其中一个数组元素 */
ushort sem_nsems; /* sem_base 数组的个数 */
time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */
time_t sem_ctime; /* 成功创建时间 */
};

 

 

struct sem {
ushort semval; /* 信号量的当前值 */
short sempid; /* 最后一次返回该信号量的进程ID 号 */
ushort semncnt; /* 等待semval大于当前值的进程个数 */
ushort semzcnt; /* 等待semval变成0的进程个数 */
};

 

2.创建打开信号量

int semget(key_t key, int nsems, int oflag)出错返回-1

if((sid=semget(keyval,numsems,IPC_CREAT|0660))==-1)
return -1;

(1) nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
(2) nsems==0 : 访问一个已存在的集合
(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
(4) 创建成功后信号量结构被设置:
.sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
.oflag 参数中的读写权限位存入sem_perm.mode
.sem_otime 被置为0,sem_ctime被设置为当前时间
.sem_nsems 被置为nsems参数的值
该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL
初始化的。
semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量
集,返回semid就是指向该信号量集的引索。

2.操作

int semop(int semid, struct sembuf *opsptr, size_t nops);
(1) semid: 是semget返回的semid
(2)opsptr: 指向信号量操作结构数组
(3) nops : opsptr所指向的数组中的sembuf结构体的个数
struct sembuf {
short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op; // 信号量操作
short sem_flg; // 操作表示符
};

3.控制

int semctl(int semid,int semnum,int cmd,[union semun arg]);

semctl(sem_id,0,SETVAL,1);

semid:信号量集合合法标识

semnum:信号量集合的特定信号量的编号

cmd 命令

arg 可选

特殊命令返回特殊值,其他命令充公返回0,失败返回-1

 

三)共享内存

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/shm.h>

  1. int shmget(key_t key, size_t size, int shmflg);成功返回值为共享内存的引用标识,失败返回-1
参数:
key : 和信号量一样,程序需要提供一个参数key,
      它有效地为共享内存段命名。
      
      有一个特殊的键值IPC_PRIVATE, 
      它用于创建一个只属于创建进程的共享内存,
      通常不会用到。
size: 以字节为单位指定需要共享的内存容量。
shmflag: 包含9个比特的权限标志,
         它们的作用与创建文件时使用的mode标志是一样。
         由IPC_CREAT定义的一个特殊比特必须和权限标志按位或
         才能创建一个新的共享内存段。
第一次创建共享内存段时,它不能被任何进程访问。
要想启动对该内存的访问,
必须将其连接到一个进程的地址空间
这个工作由shmat函数完成:
  1. void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数:
shm_id : 由shmget返回的共享内存标识。
shm_add: 指定共享内存连接到当前进程中的地址位置。
         它通常是一个空指针, 
         表示让系统来选择共享内存出现的地址。
shmflg : 是一组标志。
         它的两个可能取值是:
         SHM_RND, 和shm_add联合使用,
                  用来控制共享内存连接的地址。
         SHM_RDONLY, 它使连接的内存只读
         
返回值:
如果调用成功, 返回一个指向共享内存第一个字节的指针;
如果失败,返回-1.
 
共享内存的读写权限由它的属主(共享内存的创建者),
它的访问权限和当前进程的属主决定。
共享内存的访问权限类似于文件的访问权限。
 
3. shmdt
将共享内存从当前进程中分离
  1. int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指针。
 
成功时,返回0,
失败时,返回-1.
 
NOTE:
共享内存分离并未删除它,
只是使得该共享内存对当前进程不再可用。
 
4. shmctl
共享内存的控制函数
  1. int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds结构至少包含以下成员:
  1. struct shmid_ds {
  2.   uid_t shm_perm.uid;
  3.   uid_t shm_perm.gid;
  4.   mode_t shm_perm.mode;
  5. }
 
参数:
shm_id : 是shmget返回的共享内存标识符。
command: 是要采取的动作,
         它可以取3个值:
 
IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET   如果进程有足够的权限,
          就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID  删除共享内存段
 
buf    : 是一个指针,
         包含共享内存模式和访问权限的结构。
 
返回值:
成功时,返回0,
失败时,返回-1.

 

posted @ 2016-07-04 21:25  ranran1203  阅读(319)  评论(0)    收藏  举报