LINUX进程间通讯

linux进程间通信概述

pid_t fork( void); 头文件:#include<unistd.h>#include<sys/types.h>
(pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中)
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1 pid_t 是一个类似int的宏定义

waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束。#include<sys/types.h>#include<sys/wait.h>
pid_t waitpid(pid_t pid,int * status,int options);
如果在调用 waitpid()时子进程已经结束,则 waitpid()会立即返回子进程结束状态值。 子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数 status 可以设成 NULL。参数 pid 为欲等待的子进程识别码,
参数options 为0为阻塞方式

WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程。
子进程的结束状态返回后存于 status,底下有几个宏可判别结束情况:
WIFEXITED(status)如果若为正常结束子进程返回的状态,则为真;对于这种情况可执行WEXITSTATUS(status),取子进程传给exit或_eixt的低8位。
WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)若为异常结束子进程返回的状态,则为真;对于这种情况可执行WTERMSIG(status),取使子进程结束的信号编号。
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真;对于这种情况可执行WSTOPSIG(status),取使子进程暂停的信号编号。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。
如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回返回值-1

wait(等待子进程中断或结束#include<sys/types.h>#include<sys/wait.h>
pid_t wait (int * status);status 不是NULL时子进程的结束状态值会由参数 status 返回,而子进程的进程识别码作为函数返回值返回。
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。
用wait中止子程序会回收子程序的资源,子程序就不会进入Z(zombie僵尸状态)状态

pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。#include<pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t attr,(void)(start_rtn)(void),void arg);
用途:pthread_t用于声明线程ID。
sizeof(pthread_t) =8
pthread_t,在使用printf打印时,应转换为u类型。
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
线程创建成功,则返回0。若线程创建失败,则返回出错编号,并且
thread中的内容是未定义的。

函数pthread_join用来等待一个线程的结束,线程间同步的操作。头文件 : #include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。

在Unix类的操作系统中,调用sleep()函数需要一个以秒为单位的参数,需要更精确的时间控制可以使用nanosleep()函数。Sleep函数可以使计算机程序(进程,任务或线程)进入休眠,使其在一段时间内处于非活动状态。当函数设定的计时器到期,或者接收到信号、程序发生中断都会导致程序继续执行。

usleep函数能把线程挂起一段时间, 单位是微秒(千分之一毫秒)。本函数可暂时使程序停止执行。参数 micro_seconds 为要暂停的微秒数(us)。

1.什么是进程中通信
2.什么是线程间通信
进程间通信:在用户空间实现进程通信是不可能得到,通过linux内核通信
线程间通信:可以在用户空间就可以实现,可以通过全局变量通讯
父子进程是独立的空间,不能再用户空间用全局变量来通信

管道通信:无名管道、有名管道(文件系统中有这样一个文件名)
信号通信:信号的发送,接收和信号的处理
IPC(inter—process communication)通信:共享内存、消息队列和信号灯
以上六种是单机下的通信(只有一个linux内核)
socket通信:存在于一个网络中两个进程的通信(两个Linux内核)
进程通信基于文件IO的思想,在内核中开辟一个通信对象,进程可对其进行读写。

1.无名管道
文件系统中无文件名(文件节点)
管道文件是一个特殊的文件,是由队列实现的。
管道文件只能由pipe函数来创建
int pipe(int fd【2】)
功能创建管道,为系统调用函数(unistd。h)
参数:就是得到的文件描述符。可见有两个文件描述符fd【0】和fd【1】,管道有一个读端fd【0】(出管道),写端fd【1】(入管道)这个规定不能变,此函数会把fd{0}fd【1】返回给参数
返回值:成功是0,出错是-1

注意:
管道是从创建在内存中的,进程结束,空间释放,管道就不存在了。
管道中的东西,读完了就删除了(管道实质是队列)
如果管道中没有东西可读,会造成进程阻塞
无名管道缺点:不能实现不是父子进程(亲缘关系)直接的通信
pipe函数一定要放在fork之前

2.有名管道 头文件#include<sys/stat.h>#include<sys/types.h>

函数形式:int mkfifo(const char*filename,mode_t mode)
创建一个管道文件
参数:文件名或文件路径(可用命令行参数或字符串),
权限,权限仍和umask有关
返回值,成功为0失败为-1
创建文件节点,管道文件只有inode号,不占磁盘空间

此时没有管道,要用open函数打开管道文件时,在内核空间才会产生一个管道

读段写端都用open 打开了管道,程序才会运行。有O_RDONLY和O_WRONLY来控制读写端。

3.信号通信
内核中已经存在信号对象,不需要在创建,有很多种信号
其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间不可以
用kill -l 指令可以看内核中有哪些信号可以用,数字代表ID号,后面的宏代表功能在这里插入图片描述
头文件 signal。h sys/types.h
int kill(pid_t,int sig)
参数:整数,要接收信号的进程的进程号
0:信号被发生到所有和pid进程在同一进程组的进程
-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号ID
返回值。成功0 出错-1
atoi()可以把字符串转成整形。例如把命令行参数转成整形供别的函数使用

信号通信的框架

信号的发送 (发送信号进程) kill raise alarm
·信号的接收 (接收信号进程) paise() sleep while(1)
·信号的处理 (接收信号进程)signal

raise:发信号给自己==kill(getpid(),sig)
头文件 signal。h sys/types。h
int raise(int sig)
参数:信号ID
返回值:成功0出错-1

alarm闹钟信号头文件unistd。h 发送14号信号
定时一段时间才会发信号进程收到信号会终止
unsigned int alarm(unsigned int seconds)
参数:指定秒数
返回值:如果调用alarm前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0;
出错返回-1

信号的接收
如果进程已经运行完就会结束就不会收到信号,所以要使用函数使进程不结束
pause()无参数头文件unistd。h
返回值成功是0出错-1
pause会使进程进入睡眠状态
while循环
sleep进程睡眠。

信号的处理
自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的处理方式
void(*signal(int signum,void(*handler)(int)))(int)
参数;signum:指定信号
handler (函数指针):SIG_INT:忽略该信号
SIG_DFL采用系统默认的方式处理信号
自定义的信号处理函数指针 告诉内核处理哪一个信号,第二个参数告诉内核采用哪种处理方式
返回值:成功:设置之前的信号处理方式
出错 -1

可以在自定义函数中完成想做的事,函数执行完后返回主进程,自定义函数的参数就是signal的参数一
exit()时间是发送一个SIGCHLD信号也可以用signal处理

共享内存

查看IPC对象 ipcs -m查看共享内存 -q查看消息队列-s查看信号灯
删除IPC对象, ipcrm -m id
key_t ftok(const char*filename char id)
参数一:指定文件路径(需要当前目录中存在这个文件)
参数 二:一个字符。一个序号用来生成key
两个进程使用相同的参数便可生成相同的KEY值

头文件sys/types。h sys/ipc。h sys/shm。h
int shmget(key_t,key,int size,int shmflg)
参数1: IPC_PRIVATE宏,用于有亲缘关系的进程或者ftok函数的返回值
参数2:共享内存区的大小
参数3:权限位,类似OPEN函数
函数返回:成功共享内存段标识符,失败-1

voidshmat(int shmid,const voidshmaddr,int shmflg)
参数一:共享内存ID号
参数二:映射到的地址,NULL为系统自动完成
参数三:SHM_RDONLY共享内存只读
默认是0 表示共享内存可读写
返回值:成功返回映射后的地址,失败:NULL
删除映射地址
int shmdt(const void*shmaddr)
参数:共享内存映射后的地址
成功0 出错-1
共享内存特点
创建之后,一直存在于内核中,直到被删除或系统关闭
共享内存和管道不一样,读取后,内容仍在其共享内存中。

in shmctl(int shmid,int cmd ,struck shmid ds*buf)
参数1:要操作的共享内存标识符
参数2:IPC_STAT(获取对象属性)
IPC_SET(设置对象属性)
IPC_RMID(删除对象)
参数3:知道IPC_STAT/IPC_SET时用以保存/设置属性
函数返回值成功0出错-1

消息队列
是一个链式队列类似链表
内核空间维护消息队列会有一个结构体包含消息的ID号,创建时间,以及第一个消息和最后一个消息的地址
每个消息中有消息类型,指向下一个消息的地址。

头文件sys/yepes。h sys/ipc。h sys/msg。h
int msgget(key_t key,int flag)
参数一:key值,与共享内存的相同
参数二:标志位,同open
返回值成功消息队列ID 出错-1

发送消息
int msgsnd (int msqid,const void*msgp,size_t size,int flag)
参数一:消息队列ID
参数2:指向消息的指针,常用结构体做消息,一般结构如下
struct mybuf
{
long type;(消息类型)
char buf【】;(消息正文)
};
参数3 :消息正文的大小
参数4:IPC_NOWAIT 消息没有发送完成函数也会立即返回
0阻塞方式,直到发送完成函数才返回
返回值成功0 出错-1

删除消息队列
int msgctr (int msgqid,int cmd,struct msqid_ds*buf)
参数1:消息队列的队列ID
参数2:IPC_SATA:读取消息队列的属性,并将其保存在buf指向的缓冲区中
IPC_SET:设置消息队列的属性,这个值取自buf
IPC_RMID:从系统中删除消息队列
参数3:buf 消息队列的缓冲区
返回值成功0出错-1

int msgrev(int msgid, viod*msgp ,size_t size,long msgtype ,int flag)
参数1:消息队列ID
参数2:接收消息的缓冲区(也是前面定义的结构体)
参数3:要接受的消息的字节数
参数4:0:接收消息队列中的第一个消息
大于0:接收消息队列中第一个类型为msgtyp 的消息
小于0:接收消息队列中类型值不大于msgtyp 的绝对值且类型最小的消息
参数5:0 zusefangs
IPC_NOWAIT若没有消息,函数立即返回,非阻塞消息
返回值成功为接收到消息的长度,失败-1
读之前用memset 清除一下缓存的内容

信号灯
信号灯是信号量的集合,对一个集合进行操作
头文件sys/types,sys/ipc。h sys/sem。h
int semget(key_t key,int nsems,int semflg)
参数1:key同上
参数2:信号灯集所包含的信号灯数目(以数组的形式管理)
参数3:访问权限
返回值 成功信号灯集ID 出错-1

int semctl(int semid,int semnum,int cmd, 。。。union semun arg(不是地址))
参数1:信号灯集ID
参数2:要修改的信号灯编号
参数3:GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC-RMID:从系统中删除信号灯集合
参数4:可有可无 也可加NULL 用设置时看设置的值多少
返回值成功0 出错-1
union semun
{
int val; SETVAL;设置信号灯的值
struct semid _dsbuf;IPC_SATA(获取对象属性)IPC_SET(设置对象属性)
unsigned short
array
struct seminfo*buf
}
初始化信号量的值要用这个共同体 定义一个联合体mysemun。val=0然后用semctl把值修改进去

信号灯用semop来实现pv操作
int smeop (int semid,struct sembuf*opsptr,size_t nops)
参数1:信号灯集ID
参数2:struct sembuf
{
short sem_num 要操作的信号灯编号
short sem_op; 0 等待,直到信号灯的值编程0
1:释放资源,V操作
-1 分配资源,P操作
short sem_flg 0阻塞,IPC_NOWAIT非阻塞,SEM_UNDO 撤销操作
};若通过kill命令把其中一个进程杀死,且该进程还没有执行V操作释放资源。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。先创建结构体,定义了结构体变量,然后改变初始值之后在调用函数。
参数3:要操作的信号灯个数
POLIX信号量
#include <semaphore.h>
函数原型:sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
参数:
name 信号灯的外部名字(文件名)
oflag 选择创建或打开一个现有的信号灯,flag参数能是0、O_CREAT(创建一个信号灯)或O_CREAT|O_EXCL(如果没有指定的信号灯就创建),如果指定了O_CREAT,
mode 权限位
value 信号灯初始值
返回值:
成功时返回指向信号灯的指针,出错时为SEM_FAILED

p操作sem_wait()
v操作sem_post()
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem)

int sem_destroy(sem_t *sem);销毁一个信号量,成功0,错误返回错误号

关闭一个信号量并没有将他从系统中删除。POSIX 有名信号量是随内核持续的:即使当前没有进程打开着某个信号量,他的值仍保持。

int sem_unlink(const char *name):从系统中删除有名信号量

int sem_close(sem_t *sem);
参数:
sem 指向信号灯的指针
返回值:
若成功则返回0,否则返回-1。

一个进程终止时,内核还对其上仍然打开着的所有有名信号灯自动执行这样的信号灯关闭操作。不论该进程是自愿终止的还是非自愿终止的,这种自动关闭都会发生。
但应注意的是关闭一个信号灯并没有将他从系统中删除。这就是说,Posix有名信号灯至少是随内核持续的:即使当前没有进程打开着某个信号灯,他的值仍然保持。

sem_unlink(count char *name);
参数:
name 信号灯的外部名字
返回值:
若成功则返回0,否则返回-1。

有名信号灯使用sem_unlink从系统中删除。
每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。

以上为有名信号量,一下是内存信号量
定义用sem_t sem

初始化在线程创建之前
int sem_init(sem_t *sem, int pshared, unsigned int value);

sem :指向信号量对象
  pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
  value : 指定信号量值的大小
返回值
sem_init() 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。

p操作sem_wait(&sem)
v操作sem_post()

发布了33 篇原创文章 · 获赞 4 · 访问量 2617

猜你喜欢

转载自blog.csdn.net/CNMNMSL1/article/details/104378541