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的进程
四、管道
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>
- int shmget(key_t key, size_t size, int shmflg);成功返回值为共享内存的引用标识,失败返回-1
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- int shmdt(const void *shm_addr);
- int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
- struct shmid_ds {
- uid_t shm_perm.uid;
- uid_t shm_perm.gid;
- mode_t shm_perm.mode;
- }

浙公网安备 33010602011771号