首页 > 代码库 > linux之多进程fork:进程通信

linux之多进程fork:进程通信

++++++++++++++++++信号机制+++++++++++++++++++

接收信号


int signal(int sig,__sighandler_t handler);
int func(int sig);

sig 指明了所要处理的信号类型,handler是SIG_IGN,SIG_DFL或者返回值为整数的函数地址。
当运行了signal函数后,进程仅仅要接收到类型为sig 的信号,就马上运行 func()函数,无论其正在运行程序的哪一部分。当func()函数运行结束后,程序返回到进程被中断的那一点继续运行。

忽略中止信号:

#include <signal.h>
int main()
{
    signal(SIGINT,SIG_IGN); /*告诉进程将 SIGINT 信号忽略*/
    cout<<"start"<<endl;
    sleep(3); /*在此期间,无法中止程序执行*/
    cout<<"end"<<endl;
    return 0;
}

捕捉中止信号:

void fun(int sig)
{
    cout<<endl;
    cout<<"ctrl-c"<<endl;
}
int main()
{
    signal(SIGINT,fun); /*告诉进程将 SIGINT 信号忽略*/
    cout<<"start"<<endl;
    sleep(3); /*中止程序执行时,调用fun函数*/
    cout<<"end"<<endl;
    return 0;
}
输出:
start
^C
ctrl-c
end

发送信号

int kill(pid_t pid,int sig);
參数 pid 指定了信号发送的对象进程。
假设 pid 为零,则信号被发送到当前进程所在的进程组的全部进程;
假设 pid 为-1,则信号按进程标识符从高到低的顺序发送给所有的进程;
假设 pid 小于-1,则信号被发送给标识符为 pid 绝对值的进程组里的全部进程。
參数 sig 指定发送的信号类型。它能够是不论什么有效的信号。
普通用户的进程仅仅能向具有与其同样的用户标识符的进程发送信号。

int n=0;
void fun(int sig)
{
    cout<<n++<<endl;
}
int main()
{
    signal(SIGUSR1,fun);受到信号运行fun函数
    pid_t pid;
    pid=fork();
    if(pid==0)
    {
        pid_t ppid=getppid();
        for(int i=0;i<5;i++){kill(ppid,SIGUSR1);sleep(1);}//发送信号
    }
    else if(pid>0)
    {
        wait(NULL);
    }
    else 
    {
        perror("fork Error!");
        exit(1);
    }
    return 0;
}
输出:
0
1
2
3
4

ALARM信号

当进程在alarm()的规定时间内没有完毕的话,就会发送一个ALARM信号

void fun(int sig)
{
    cout<<"alarm 5"<<endl;
}
int main()
{
    signal(SIGALRM,fun);
    pid_t pid;
    pid=fork();
    if(pid==0)
    {
        pid_t ppid=getppid();
        alarm(5);//设置定时器,在5秒时将发送超时信号,并终止子进程运行。
        sleep(8);
    }
    else if(pid>0)
    {
        wait(NULL);
    }
    else 
    {
        perror("fork Error!");
        exit(1);
    }
    return 0;
}

pause函数

pause等待进程捕捉并处理信号。

void fun(int sig){}
int main()
{
    signal(SIGALRM,fun);
    alarm(1);
    pause();//处于等待状态。
    return 0;
}

++++++++++++++++++管道++++++++++++++++++

实现父子进程间的通信

Pipe函数

Pipe(int fd[2])

fd用于存放 pipe()函数新建立的管道句柄

fd[0]是从管道中读出数据的句柄

fd[1]是向管道写入数据的句柄。即fd[1]的写入由 fd[0]读出。

int main()
{
    int fd[2];
    char buf[100]={'\0'};
    pid_t pid;
    pipe(fd);//建立管道
    pid=fork();//建立子进程
    if(pid>0)
    {
        close(fd[1]);//关闭不须要的句柄
        read(fd[0],buf,100);
        cout<<buf<<endl;
    }
    else if(pid==0)
    {
        close(fd[0]);//关闭不须要的句柄
        strcpy(buf,"hello");
        write(fd[1],buf,100);
    }
    else {exit(1);}
    return 0;
}

dup函数

int dup(int oldfd);

将管道句柄重定向到标准输入/输出上最小的未使用的句柄.
在使用dup函数前最好将原句柄关闭.

int main()
{
    int fd[2];
    char buf[100];
    char tmp[100];
    pid_t pid;
    pipe(fd);
    pid=fork();
    if(pid>0)
    {
        close(fd[1]);
        close(0);//关闭标准输入,0句柄是最小未使用句柄.
        dup(fd[0]);
        cin>>buf;
        cout<<buf<<endl;
    }
    else if(pid==0)
    {
        close(fd[0]);
        close(1);//关闭标准输出,1句柄是最小未使用句柄.
        dup(fd[1]);
        strcpy(tmp,"hello");
        cout<<tmp<<endl;
    }
    else {exit(1);}
    return 0;
}

dup2函数


int dup2(int oldfd,int newfd);

dup2函数相当于 
close(0);
dup(fd[0]);
这两条语句.

int main()
{
    int fd[2];
    char buf[100]={'\0'};
    char tmp[100]={'\0'};
    pid_t pid;
    pipe(fd);
    pid=fork();
    if(pid>0)
    {
        close(fd[1]);
        dup2(fd[0],0);
        cin>>buf;//从管道读入
        cout<<buf<<endl;
    }
    else if(pid==0)
    {
        close(fd[0]);
        dup2(fd[1],1);
        strcpy(tmp,"hello");
        cout<<tmp<<endl;//读出到管道
    }
    else {exit(1);}
    return 0;
}

popen()/pclose()函数

FILE *popen(char *command,char *type);
int pclose(FILE *stream);

popen()函数首先调用 pipe()函数建立一个管道,然后用 fork()函数建立一个子进程,执行一个 shell 环境,然后在这个 shell 环境中执行"command"參数指定的程序。数据在管道中流向由"type"參数控制。这个參数能够是"r"或者"w",分别代表读和写。

int main()
{
    FILE *pipe_fp;
    if((pipe_fp = popen("ps -ef","r"))== NULL)//设置一个管道读.
    {
        perror("popen");
        exit(1);
    }
    char buf[100]={'\0'};
    fread(buf,1,99,pipe_fp);//从管道中读.
    cout<<buf<<endl;
    /* 关闭管道 */
    pclose(pipe_fp);
    return(0);
}

++++++++++++++++有名管道++++++++++++++++

解决管道不能提供非父/子关系进程间通信的缺陷.
在读取数据时,若管道中没有数据,有名管道会自己主动堵塞直到读取到数据为止.

int mknod(char *pathname,mode_t mode,dev_t dev);
pathname:要创建的文件的名称;
mode:文件类型;
dev:该文件相应的设备文件的设备号。仅仅有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0就可以。


例如以下:S_IFIFO表示要创建一个FIFO文件,0666表示该文件的权限是全部人可读可写,0表示该文件不是一个设备文件。
管道读
#include <sys/stat.h>
#include <unistd.h>
int main()
{
    char buf[100]={'\0'};
    mknod("./tmpss",S_IFIFO|0666,0);
    FILE *fp=fopen("./tmpss","r");
    fgets(buf,100,fp);//堵塞等待读取数据
    fclose(fp);
    cout<<buf<<endl;
    return(0);
}
管道写.
int main()
{
    FILE *fp = fopen("./tmpss","w")
    fputs("hello",fp);
    fclose(fp);
    return(0);
}

++++++++++++++++++文件锁++++++++++++++++++

文件和记录锁定可分为咨询式锁定和强制锁定两种。

System V

int lockf(int fd,int function,long size);
function能够是下面取值:
F_ULOCK:为一个先前锁定的区域解锁
F_LOCK:锁定一个区域,假设指定文件的相应区域已被其他进程锁定,那么 lockf 的调用进程将被堵塞直到该区域解锁。
F_TLOCK:測试并锁定一个区域,假设被測试的区域上了锁,lockf便会马上返回-1,出错返回码 errno 将为 EAGAIN.
F_TEST:測试一个区域是否已经上锁。
參数 size 指明了从文件当前位置開始的一段连续锁定区域的长度,当 size 为 0 时,锁定记录将由当前位置一直扩展到文件尾。
#include <unistd.h>
int main()
{
    int fd;
    char buf[2]={'\0'};
    if((fd=open("data",O_RDWR))<=0)
    {
        perror("Can't open");
        exit(1);
    }
    if(lockf(fd,F_LOCK,0)!=-1)//堵塞直到成功加锁
    {
        int i=0;
        while(i++<3)
        {
            lseek(fd,0,0);
            read(fd,buf,1);
            buf[0]+=1;
            cout<<buf<<endl;
            lseek(fd,0,0);
            write(fd,buf,1);
        }
	ockf(fd,F_ULOCK,0);
    }
    close(fd);
    return(0);
}
连续运行两次输出
start
2
3
4
start
5
6
7
假设改为F_TLOCK,则仅仅有第一个进程的输出.第二个进程没有输出.


BSD

int flock(int fd,int operation);
调用 flock 有两个參数:
fd:一个已打开文件的文件描写叙述符;
operation能够是下面取值:
LOCK_SH:共享锁
LOCK_EX:相互排斥锁
LOCK_UN:解锁
LOCK_NB:当文件已被锁定时不堵塞
#include <sys/file.h>
int main()
{
    int fd;
    char buf[2]={'\0'};
    if((fd=open("data",O_RDWR))<=0)
    {
        perror("Can't open");
        exit(1);
    }
    if(flock(fd,LOCK_EX)!=-1)//设置堵塞性相互排斥锁
    {
        int i=0;
        while(i++<3)
        {
            lseek(fd,0,0);
            read(fd,buf,1);
            buf[0]+=1;
            cout<<buf<<endl;
            lseek(fd,0,0);
            write(fd,buf,1);
        }
	flock(fd,F_ULOCK);
    }
    close(fd);
    return(0);
}

连续运行两次输出:
start
2
3
4
start
5
6
7
假设是LOCK_EX | LOCK_NB,则仅仅有第一个进程的输出.第二个进程没有输出.

++++++++++++++++消息队列++++++++++++++++


System V

key_t ftok(char *pathname,char proj);
获取一个消息队列的标识符。
pathname和proj能够任意指定,不同的<pathname,proj>产生不同的标识符.收发消息两方应使用同样的<pathname,proj>.

假设是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来取代。假设两个进程没有不论什么关系,就用ftok()。


int msgget(key_t key,int msgflg)
创建新的消息队列或获取已有的消息队列。
key:消息队列对象的keyword(key).
msgflg能够是下面取值:
IPC_CREAT:假设消息队列对象不存在,则创建,否则进行打开操作;
IPC_EXCL:和IPC_CREAT一起使用(用”|”连接),保证所得的消息队列对象是新创建的而不是打开已有的对象。


int msgsnd(int msqid,struct msgbuf *msgp,int msgsz,int msgflg)
msqid 是消息队列对象的标识符.
msgp 指向要发送的消息所在的内存.
msgsz 是要发送信息的长度(字节数),能够用公式计算:msgsz = sizeof(struct mymsgbuf)- sizeof(long);
msgflg能够是下面取值:
0,忽略标志位;
IPC_NOWAIT,假设消息队列已满,消息将不被写入队列,马上返回。假设不指定这个參数,线程将被堵塞直到消息被能够被写入。


int msgctl(int msgqid, int cmd, struct msqid_ds *buf)
msgqid:消息队列对象的标识符。
cmd能够是下面取值:
IPC_STAT:取出系统保存的消息队列的 msqid_ds 数据,并将其存入參数 buf 指向的 msqid_ds 结构中。
IPC_SET:设定消息队列的 msqid_ds 数据中的 msg_perm 成员。设定的值由 buf 指向的 msqid_ds结构给出。
IPC_EMID:将队列从系统内核中删除。


发送消息:

#include <sys/msg.h>
struct mymsgbuf {
	long msgtype;
	char msgtext[100]={'\0'};
};
int main()
{
	mymsgbuf buf;
	buf.msgtype=1;
	strcpy(buf.msgtext,"hello");
    key_t key = ftok(".",'a');
    int msgqueue_id = msgget(key, IPC_CREAT|IPC_EXCL|0660);
	msgsnd(msgqueue_id,(mymsgbuf*)&buf,sizeof(buf.msgtext),0);
    return(0);
}
接受消息:
struct mymsgbuf {
    long msgtype;
    char msgtext[100]={'\0'};
};

int main()
{
    mymsgbuf buf;
    key_t key = ftok(".",'a');
    int msgqueue_id = msgget(key, IPC_CREAT|0660);
    msgrcv(msgqueue_id,(mymsgbuf*)&buf,100,1,0);
    msgctl(msgqueue_id, IPC_RMID, 0);
    cout<<buf.msgtext<<endl;
    return(0);
}
输出:
hello


POSIX 标准

POSIX名字规则(即open函数第一个參数规则):
1、必须以‘/’开头
2、出去第一个‘/’外,不能有其它‘/’


mqd_t mq_open(const char * name,int flags,/*mode_t mode,struct mg_attr *attr */)
name:即上面所说名字
flags: 打开消息队列的权限
mode: 创建消息队列时的权限
attr:创建消息队列打属性


成功返回消息队列描写叙述符,失败返回-1

int mq_close(mqd_t mqd)
成功返回0,失败返回-1
关闭消息队列,(没有删除)


int mq_unlink(mqd_t mqd)
删除此消息队列名,而消息队列在引用计数为0时销毁

int mq_getattr(mqd_t mqd,struct mq_attr *attr);
int mq_setattr(mqd_t mqd,struct mq_attr *attr,struct mq_attr *oattr);
成功返回0,失败返回-1
分别时获取所指消息队列的属性

struct mqd_attr 
{
	long mq_flags; 0或者O_NONBLOCK
	long mq_maxmsg; 最大消息数
	long mq_msgsize;每条消息最大size
	long mq_curmsgs;当前队列消息数
};

mq_send(mqd_t mqd,const char * buf,size_t len,unsigned int pro);
mq_receive(mqd_t mqd,char * buf,size_t len,unsigned int *pro);
发送接收消息,最后一个參数(非负整数)设定/获取此消息的优先级。

编译时须要指定对应函数库-lrt

创建消息队列并发送消息

#include <iostream>
#include <stdio.h>
#include <errno.h>
#include <mqueue.h>
#include <unistd.h>

using namespace std;

int main()
{
	freopen("in.txt","r",stdin);
	//消息队列属性
	struct mq_attr attr;
	attr.mq_flags=1;
	attr.mq_maxmsg=5;
	attr.mq_msgsize=100;
	mqd_t mqd=mq_open("/mqd",O_WRONLY|O_CREAT,S_IWUSR|S_IRUSR,&attr);
	// mqd_t mqd=mq_open("mqd",O_WRONLY);

	//设置消息队列属性
	attr.mq_flags=0;
	mq_setattr(mqd,&attr,NULL);

	char sendbuf[100];
	//发送消息
	fgets(sendbuf,100,stdin);
	mq_send(mqd,sendbuf,100,0);

	fgets(sendbuf,100,stdin);
	mq_send(mqd,sendbuf,100,0);

	fgets(sendbuf,100,stdin);
	mq_send(mqd,sendbuf,100,0);
	//关闭消息队列
	mq_close(mqd);
	return 0;
}


接收消息并删除消息队列

#include <iostream>
#include <mqueue.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;
int main()
{
	struct mq_attr attr;
	//仅仅读打开消息队列
	mqd_t mqd=mq_open("/mqd",O_RDONLY);
	mq_getattr(mqd,&attr);
	int bufsize=attr.mq_msgsize;
	//接收消息的buf必须大于等于消息队列设定的每一条消息大小
	char recvbuf[bufsize];
	int recv_len=0;
	while(attr.mq_curmsgs>0)
	{
		recv_len=mq_receive(mqd,recvbuf,bufsize,NULL);
		if(recv_len>=0)
		{
			cout<<recvbuf<<endl;
			mq_getattr(mqd,&attr);
		}
		else break;
	}
	//关闭并删除消息队列
	mq_close(mqd);
	mq_unlink("mqd");
	return 0;
}




++++++++++++++++++信号量++++++++++++++++++


结构体:
struct sembuf {
	unsigned short sem_num;
	short sem_op;
	short sem_flg;
};

sem_num 成员为接受操作的信号量在信号量数组中的序号(数组下标)。

sem_op 成员定义了进行的操作(能够是正、负和零)。

sem_flg 是控制操作行为的标志。

假设 sem_op 是负值,就从指定的信号量中减去对应的值。

假设 sem_op 是正值,就在指定的信号量中加上对应的值。

假设 sem_op 是零,那么调用 semop()函数的进程就会被堵塞直到相应的信号量值为零。

int semget(key_t key, int nsems, int semflg);
建立新的信号量对象或者获取已有对象的标识符。
nsems:指定新生成的信号量对象中信号量的数目。
key:消息队列对象的keyword(key).
semflg能够是下面取值:
IPC_CREAT:假设消息队列对象不存在,则创建,否则进行打开操作;

IPC_EXCL:和IPC_CREAT一起使用(用”|”连接),保证所得的消息队列对象是新创建的而不是打开已有的对象。


int semop(int semid, struct sembuf *sops, unsigned nsops);
改变信号量对象中各个信号量的状态。
semid:信号量对象的标识符。
sops:sembuf数组,定义semop()函数所要进行的操作序列。

nsops:sops数组的长度。


一个进程创建并使用信号量:
#include <sys/sem.h>
union semun 
{ 
	int val; 
	struct semid_ds *buf; 
	unsigned short int *array; 
	struct seminfo *__buf; 
};

int main()
{
	key_t mykey = ftok(".",'b');
	sembuf sops1{0,-1,0};
	sembuf sops2{0,1,0};
	semun semopts;
	semopts.val = 1;
	int sid = semget(mykey, 1, IPC_CREAT | 0660);
	semctl(sid, 0, SETVAL, semopts);
	semop(sid, &sops1,1);
	sleep(5);
	cout<<"do something"<<endl;
	semop(sid, &sops2,1);
}

还有一个进程使用信号量:

int main()
{
    key_t mykey = ftok(".",'b');
    sembuf sops1{0,-1,0};
    sembuf sops2{0,1,0};
    int sid = semget(mykey, 1, IPC_CREAT | 0660);
    semop(sid, &sops1,1);
    cout<<"another do something "<<endl;
    semop(sid, &sops2,1);
    semctl(sid, 0, IPC_RMID, 0);
}

输出:
do something
another do something

++++++++++++++++共享内存++++++++++++++++

posix 标准

int shm_open(const char* name,int flags,mode_t mode);
成功返回非负描写叙述符失败返回-1
int shm_unlink(const chat * name);
成功返回0,失败-1
int ftruncate(int fd,off_t len);
改动内存区大小
int fstat(int fd,const stat *buf)
获取内存对象信息
void *mmap(void* addr,size_t len,int prot,int flags,int fd,off_t offset);
addr:通常为空指针,意为让内核自行决定内存地址
len:映射字节数,从offset開始
prot:对共享内存的权限,取值例如以下:
PROT_READ:可读
PROT_WRITE:可写
PROT_EXEC:可运行
PROT_NONE:不可訪问
flags:有下面取值:
MAP_SHARED:变动共享,即内存的改动对其它进程可见
MAP_PRIVATE:变动私有,即内存的改动对其它进程不可见
MAP_FIXED:准确的解释addr參数,考虑到可移植性,此參数不应该指定
成功返回被映射内存的起始地址,出错则为MAP_FAILED
mmap成功返回后,fd描写叙述符能够关闭


int munmap(void *addr,size_t len)
删除共享内存
此时,假设共享内存是MAP_PRIVATE,那么进程所做的变动,内核将丢弃
若是MAP_SHARED,则内存与文件会定时同步


int msync(void *addr,size_t len,int flags);
flags:取值例如以下:
MS_ASYNC:异步写,提交写操作请求给内核,然后函数马上返回
MS_SYNC:同步写,等到写操作完毕后,函数才返回
MS_INVALIDATE:使快速缓存的数据失效
成功返回0,失败-1
将内存变动马上同步到文件(磁盘)

演示样例:
send.cpp
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h> 
using namespace std;


int main()
{
	int zero=0;
	//创建信号量
	sem_t *semd=sem_open("in1",O_CREAT,S_IRUSR|S_IWUSR,1);
	//创建共享内存区
	int fd=shm_open("in",O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
	//初始化内存区为0
	write(fd,&zero,sizeof(int));
	//映射共享内存到对应地址空间
	int *num=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	close(fd);
	for(int i=0;i<5;i++)
	{
		sem_wait(semd);
		cout<<(*num)++<<'\t';
		sem_post(semd);
	}
	sem_close(semd); 
	return 0;
}

recv.cpp
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h> 
using namespace std;

int main()
{
	int zero=0;
	//打开信号量
	sem_t *semd=sem_open("in1",0,S_IRUSR|S_IWUSR,1);
	//打开共享内存区
	int fd=shm_open("in",O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
	int *num=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	for(int i=0;i<5;i++)
	{
		sem_wait(semd);
		cout<<(*num)++<<'\t';
		sem_post(semd);
	}
	sem_close(semd); 
	sem_unlink("in1");
	shm_unlink("in");
	return 0;
}

system v

int shmget(key_t key, int size, int shmflg);
创建新的或取得已有的共享内存。
key 是共享内存的keyword;
size 是创建的共享内存的大小,以字节为单位。
shmflg 是控制函数行为的标志量,同 msgget()及 semget()函数.

int shmat(int shmid, char *shmaddr, int shmflg);
将shmget()函数得到的共享内存,映射到进程自己的内存空间内。
第一个參数是共享内存的标识符。
第二个參数 shmaddr 指定了共享内存映射的地址。
shmflg:
SHM_RND 标志来强迫将内存大小设定为页面的尺寸。
SHM_RDONLY 共享内存将被映射成仅仅读。


int shmctl(int shmqid, int cmd, struct shmid_ds *buf);
cmd:
PC_STAT 获得共享内存的信息。
IPC_SET 设定共享内存的信息。
IPC_RMID 删除共享内存。

int shmdt(char *shmaddr);

断开共享内存映射


一个进程发送数据

#include <sys/shm.h>
int main()
{
	key_t mykey = ftok(".",'b');
	int shmid = shmget(mykey, 100, IPC_CREAT|IPC_EXCL|0666);
	char *segptr = (char *)shmat(shmid, 0, 0);
	strcpy(segptr, "hello");
}

一个进程读取数据
int main()
{
    key_t mykey = ftok(".",'b');
    int shmid = shmget(mykey, 100, 0);
    char *segptr = (char *)shmat(shmid, 0, 0);
    cout<<segptr<<endl;
    shmctl(shmid, IPC_RMID, 0);
}
输出:
hello



int shm_open(const char* name,int flags,mode_t mode);
成功返回非负描写叙述符失败返回-1

int shm_unlink(const chat * name);
成功返回0,失败-1

int ftruncate(int fd,off_t len);
改动内存区大小

int fstat(int fd,const stat *buf)
获取内存对象信息

linux之多进程fork:进程通信