前言:进程间的通信方式包括
IPC:
1、管道
pipe 无名管道
fifo 有名管道
2、信号 signal
3、消息队列 System V消息队列 / POSIX消息队列
4、共享内存 System V共享内存 / POSIX共享内存
5、信号量 System V信号量 / POSIX信号量 <-----------
6、socket套接字
1、引入
例子:
int i = 0;
func()
{
i++;
}
当有两个任务,调用func()函数时,执行之后i的值为多少?
答: 不确定的,因为i++ 是一个非原子操作
可以分割成3个步骤:
(1)读取
(2)计算
(3)回写
====================
ps. 原子操作 Atomic Operation
一般定义为 在执行过程中不会被线程调度机制中断的操作
这种操作一旦开始 就会一直运行到结束,中间不会被其他的线程的操作所打断
原子操作是不可分割的,即操作要么完全执行,要么完全不执行,不存在执行一半的情况
非原子操作与之相反
综上所述如果有两个或者多个任务(进程/线程),去访问同一个共享资源(硬件/软件) 那么就必须要保证这个共享资源的 被有序访问。
2、信号量 semaphore
信号量
是用于 不同进程之间 或者 同一个进程中不同线程之间 的同步机制
同步 ---> 有序、有条件的访问
信号量的出现,就是为了保护共享资源,让共享资源被有序的访问
信号量 是用来表示一种资源的数量
是一种特殊的计数器,用于控制对共享资源的访问
什么时候需要信号量?
需要保护对象的时候,就需要信号量
“保护”:让被保护的对象(共享资源)被有序访问
3、如何保护?
信号量机制 其实是对程序设计者的约束,用来保护共享资源的
例子:
进程A 进程B 都需要访问同一个“互斥”设备
那么就用一个信号量来表示能不能访问该设备,然后每一个进程在访问这个设备之前,
都先去访问这个信号量:
如果能够访问该设备,就先将信号量调成“NO”,然后再去访问这个互斥设备,
访问完之后,最后再将该信号量调成“YES”
信号量如何实现?
一个进程(或线程)在某一个信号量上 执行3个操作:
(1)创建一个信号量,还必须要求 调用者指定信号量的初始值
初始值就表示给信号量保护的共享资源 可以被多少个任务访问
sem --> 1 表示可以被1个进程或者线程去访问
sem --> 5 表示可以被5个进程或者线程去访问
(2)等待一个信号量,该操作会测试这个信号量的值
如果其值小于或者等于0 那么就会阻塞(等待)
一旦它的值变成大于0 就会先将它-1 在继续执行访问它的代码
while( sem_value <= 0 )
{
wait
}
sem_value --;
访问
p操作 proberen (尝试 荷兰语)
lock 上锁
(3)释放一个信号量,将信号量的值+1
sem_value ++;
v操作 verhogen (增加)
unlock 解锁
信号量保护:
在临界区的前面 加上一个p操作,然后在临界区的后面 加上一个v操作
“临界区”: 把操作“共享资源”的代码区域 称为 临界区
p
xxx //临界区
v
4、信号量的实现
System V semaphore
POSIX semaphore
POSIX 有名 semaphore
POSIX 无名 semaphore
4.1)System V semaphore
System V 信号量 ---> 计数信号量集 (信号量数组)
存在于 内核中
对于系统中的每个信号量集,内核都会维护在如下的结构体中:
struct semid_ds
{
struct ipc_perm sem_perm; /* 权限 Ownership and permissions */
time_t sem_otime; /* 最后调用semp()的时间 Last semop time */
time_t sem_ctime; /* 最后修改的时间 Last change time */
unsigned long sem_nsems; /* 信号量数组中有多少信号量 No. of semaphores in set */
};
接口函数:
(1)获取键值key
ftok()
(2)创建或者打开一个System V信号量集
semget()
NAME
semget - get a System V semaphore set identifier
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建或者打开一个System V信号量集
参数:
key:键值
nsems:指定要创建的信号量集中 信号量的个数
semflg: 标志位
(1)创建标志
IPC_CREAT | 权限位
例子:
IPC_CREAT | 0666
注意:
如果创建失败的原因 是因为已经存在了
且 创建的标志为 IPC_CREAT | IPC_EXCL 一起使用
此时 errno == EEXIST
(2)打开标志
0
返回值:
成功,返回该信号量集的id
失败,返回-1,同时errno被设置
注意:
在创建一个信号量集时,信号量的值是不确定的
因此 我们在创建一个信号量集之后,要马上指定它们的初始值
(3)控制操作
semctl
NAME
semctl - System V semaphore control operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:对System V信号量集的控制操作
参数:
semid: 信号量集id
semnum:指定要操作哪个信号量,就是信号量数组的下标(0,1,2,...)
cmd: 命令号
IPC_RMID 删除
IPC_STAT 获取属性
IPC_SET 设置属性
GETVAL 获取某个信号量的值
SETVAL 设置某个信号量的值
GETALL 获取所有信号量的值
SETALL 设置所有信号量的值
...
...:第四个参数,根据cmd的不同,第四个参数也不相同
第四个参数的类型:
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};
//设置信号量数组中的第一个元素的初始值
union semun a;
a.val = 2;
semctl( sem_id, 0, SETVAL, a.val );
=============================
如果 cmd == IPC_RMID 删除信号量集
第四个参数就不要了
=============================
如果 cmd == GETVAL 获取某个信号量的值,第四个参数也不要
函数的返回值 就是该信号量的值
例子:
int b = semctl( sem_id, 0, GETVAL );
=============================
如果 cmd == SETVAL 设置某个信号量的值,第四个参数为int类型
例子:
设置信号量集中第二个的信号量的值为10
int a = 10;
semctl( sem_id, 1, SETVAL, a );
==============================
如果 cmd == GETALL 获取整个信号量集中的 所有信号量的值
第四个参数的类型为 unsigned short []
用来存放所获取到的信号量集的值
例子:
unsigned short arr[6];
semctl( sem_id, 0, GETALL, arr );
==============================
如果 cmd == SETALL 设置整个信号量中的 所有信号量的值
第四个参数的类型为 unsigned short []
用来存放要设置的信号量集的值
例子:
unsigned short arr[6] = {1,1,0,2,3,4};
semctl( sem_id, 0, SETALL, arr );
练习:
创建一个System V信号量集 (5个)
并 给这个信号量集设置初始值, 并 获取第三个信号量的值并打印
#define PATHNAME "/home/china/"
int main()
{
//1.获取键值key
key_t key = ftok( PATHNAME, 7 );
if( key == -1 )
{
perror("ftok error ");
return -1;
}
printf("key = 0x%x\n", key );
//2.创建或打开一个信号量集
int sem_id = semget( key, 5, IPC_CREAT | IPC_EXCL | 0666 );
if( sem_id == -1 )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
sem_id = semget( key, 5, 0 );
}
else
{
perror("semget error ");
return -1;
}
}
printf("sem_id = %d\n", sem_id );
//3.控制操作
//设置初始值 SETALL
unsigned short arr[5] = {0,2,4,6,8};
semctl( sem_id, 0, SETALL, arr );
//获取第三个信号量的值并打印 GETVAL
int x = semctl( sem_id, 2, GETVAL );
printf("x = %d\n", x );
for( int i=0; i<5; i++ )
{
printf("%d ", semctl( sem_id, i, GETVAL ) );
}
putchar('\n');
}
(4.1.2)信号量p/v操作
semop()
NAME
semop - System V semaphore operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:对System V信号量集的p/v操作
参数:
semid: 信号量集id
sops:结构体指针,信号量p/v操作的结构体数组
可能同时对多个信号量进行操作
在System V信号量集中,对某个信号量的操作 用结构体来描述的
struct sembuf
{
unsigned short sem_num; /* 指定要操作的信号量(信号量数组的下标) semaphore number */
short sem_op; /* 操作 semaphore operation */
sem_value = 原来sem_value + sem_op;
sem_op < 0 ---> - p操作
sem_op > 0 ---> + v操作
sem_op == 0 ---> 如果sem_value==0 立即返回
如果sem_value!=0 看阻塞还是非阻塞
short sem_flg; /* 操作标志 operation flags */
0 阻塞
IPC_NOWAIT 非阻塞
SEM_UNDO 撤销 ,为了防止进程“带锁退出”
如果设置了SEM_UNDO 那么在进程退出时
内核会自动释放该进程持有的信号量
}
nsops:第二个参数 需要操作的信号量的个数
表示你要对多少个信号量进行操作
返回值:
成功,返回0
失败,返回-1,同时errno被设置
例子:
// p操作 上锁
struct sembuf buf;
buf.sem_num = 0; //指定要操作的信号量(信号量数组的下标)
buf.sem_op = -1; //-1 p操作
buf.sem_flg = 0; //阻塞
semop( sem_id, &buf, 1 );
//临界区 (对共享资源的访问)
//...
// v操作 解锁
buf.sem_num = 0; //指定要操作的信号量(信号量数组的下标)
buf.sem_op = +1; //+1 v操作
buf.sem_flg = 0; //阻塞
semop( sem_id, &buf, 1 );
练习:
创建一个System V信号量集 (5个)
并 给这个信号量集设置初始值, 并 获取第三个信号量的值并打印
#define PATHNAME "/home/china/"
int main()
{
//1.获取键值key
key_t key = ftok( PATHNAME, 7 );
if( key == -1 )
{
perror("ftok error ");
return -1;
}
printf("key = 0x%x\n", key );
//2.创建或打开一个信号量集
int sem_id = semget( key, 5, IPC_CREAT | IPC_EXCL | 0666 );
if( sem_id == -1 )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
sem_id = semget( key, 5, 0 );
}
else
{
perror("semget error ");
return -1;
}
}
printf("sem_id = %d\n", sem_id );
//3.控制操作
//设置初始值 SETALL
unsigned short arr[5] = {0,2,4,6,8};
semctl( sem_id, 0, SETALL, arr );
//获取第三个信号量的值并打印 GETVAL
int x = semctl( sem_id, 2, GETVAL );
printf("x = %d\n", x );
for( int i=0; i<5; i++ )
{
printf("%d ", semctl( sem_id, i, GETVAL ) );
}
putchar('\n');
}
4.2)POSIX semaphore ----> 单个信号量
POSIX 有名信号量
可以用于任意的进程之间 或者 任意的线程之间
在文件系统中有一个名字(入口), 但是 信号量的值存在于内核中
POSIX 无名信号量
线程 和 子进程之间
没有名字,基于内存的信号量
如果这段空间 在“内核中的共享内存中”, 进程可以访问,线程也可以访问
如果这段空间 在“进程的地址空间中”, 只能用于该进程内部所有的线程的同步
(4.2.1) 接口函数:
(1)创建或者打开一个POSIX信号量
POSIX信号量 用类型 sem_t 来表示
(1.1)创建并初始化一个POSIX有名信号量
sem_open()
NAME
sem_open - initialize and open a named semaphore
SYNOPSIS
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
功能:创建并初始化一个POSIX有名信号量
参数:
name: 指定要创建或者打开的POSIX有名信号量的在文件系统中的路径名
以 / 开头的路径名 (实际生成在 /dev/shm/ 目录下)
例如:
"/data" 读信号量
"/space" 写信号量
oflag:标志位
(1)创建标志
O_CREAT
要判断文件是否存在 O_CREAT | O_EXCL
(2)打开标志
0
mode:权限
(1)宏
S_IRUSR S_IWUSR S_IXUSR S_IRGRP S_IWGRP S_IXGRP S_IROTH S_IWOTH S_IXOTH
(2)八进制
0666
value:指定该信号量的初始值
返回值:
成功,返回 sem_t* 指针, 指向了一个已经打开的POSIX有名信号量
失败,返回 SEM_FAILED 指针,同时errno被设置
注意:
Link with -pthread.
编译时 要链接库 -pthread // pthread ---> posix thread
例子:
sem_t * space = sem_open( "/space", O_CREAT | O_EXCL, 0666, 1 );
if( space == SEM_FAILED )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
space = sem_open( "/space", 0 );
}
else
{
perror("sem_open space error ");
return -1;
}
}
(4.2.2)初始化一个POSIX无名信号量
sem_init()
初始化一个POSIX无名信号量,需要实现分配一个sem_t的空间
sem_t st; 或者 sem_t * st = malloc( sizeof(sem_t) );
NAME
sem_init - initialize an unnamed semaphore
SYNOPSIS
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个POSIX无名信号量
参数:
sem:要初始的POSIX无名信号量的地址
pshared:指定该无名信号量的共享方式
0 进程内部的线程之间的访问
1(非0) 不同的进程直接的访问
如果是1这个情况,要保证sem指向的空间是不同进程都可以访问的
---》 sem指向的空间,只能是共享内存
value:指定该信号量的初始值
返回值 :
成功,返回0
失败,返回-1,同时errno被设置
ps. 注意在编译时需要 加上 -pthread 使其与库连接
(4.2.3)POSIX信号量的p/v操作
(2.1) p操作
sem_wait()
NAME
sem_wait, sem_timedwait, sem_trywait - lock a semaphore
SYNOPSIS
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:用来获取sem指定的信号量,一直阻塞等待,直到申请到资源
参数:
sem:指定要操作的信号量
返回值:
成功,返回0
失败,返回-1,同时errno被设置
int sem_trywait(sem_t *sem); //非阻塞
功能:尝试获取sem指定的信号量,如果资源可用,立即返回,否则返回错误
能获取则获取(返回0),不能获取则立即返回(返回-1)
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
功能:用来获取sem指定的信号量,限时等待
参数:
sem:指定要操作的信号量
abs_timeout:绝对时间
获取当前的时间(距离1970年1月1日00:00:00的秒数) + 愿意等待的时间
struct timespec
{
time_t tv_sec; /* 秒 seconds */
long tv_nsec; /* 纳秒 nanoseconds */
};
1s == 1000ms == 1000000us == 1000000000ns
==============================
ps. clock_gettime() 获取当前时间
NAME
clock_gettime - clock and time functions
SYNOPSIS
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespec *tp);
功能:获取当前时间
参数:
clk_id:指定要获取的时间源 CLOCK_REALTIME
tp:指向一个timespec结构体的指针,用来存放时间
返回值:
成功,返回0
失败,返回-1,同时errno被设置
例子:
愿意等待5秒40毫秒
//获取当前时间
struct timespec ts;
clock_gettime( CLOCK_REALTIME, &ts );
//当前时间 + 愿意等待的时间
ts.tv_sec += 5;
ts.tv_nsec += 40000000;
if( ts.tv_nsec >= 1000000000 ) //进位
{
ts.tv_sec ++;
ts.tv_nsec -= 1000000000;
}
(2.2) v操作
sem_post()
NAME
sem_post - unlock a semaphore
SYNOPSIS
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:释放sem指定的信号量, 即 对指定的信号量 进行 v操作
参数:
sem:指定要操作的信号量
返回值:
成功,返回0
失败,返回-1,同时errno被设置
(4.2.4)用来获取信号量的值
sem_getvalue()
NAME
sem_getvalue - get the value of a semaphore
SYNOPSIS
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
功能:用来获取指定信号量的值
参数:
sem:指定要操作的信号量
sval:指针,指向的空间用来存放信号量的值
返回值:
成功,返回0
失败,返回-1,同时errno被设置
(4.2.5)POSIX有名信号量的 关闭和删除操作
关闭 sem_close()
NAME
sem_close - close a named semaphore
SYNOPSIS
#include <semaphore.h>
int sem_close(sem_t *sem);
功能:关闭一个POSIX有名信号量
参数:
sem:指定要关闭的POSIX有名信号量
返回值:
成功,返回0
失败,返回-1,同时errno被设置
删除 sem_unlink() (尽量把之前创建的信号量删除)
NAME
sem_unlink - remove a named semaphore
SYNOPSIS
#include <semaphore.h>
int sem_unlink(const char *name);
功能:删除一个POSIX有名信号量
参数:
name:指定要删除的POSIX有名信号量的在文件系统中的路径名
返回值:
成功,返回0
失败,返回-1,同时errno被设置
(4.2.6)POSIX 无名信号量的销毁
sem_destroy()
NAME
sem_destroy - destroy an unnamed semaphore
SYNOPSIS
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁一个POSIX无名信号量
参数:
sem:指定要销毁的POSIX无名信号量
返回值:
成功,返回0
失败,返回-1,同时errno被设置
练习:
把 之前的练习题 改用 POSIX有名信号量 进行保护
posix_sem1.c 循环的依次写字符串s的每一个字符 到共享内存中,每次写一个
posix_sem2.c 不断地从共享内存中去读取一个字符 并 打印
int main()
{
//1.获取键值key -- 共享内存
key_t key1 = ftok( PATHNAME, 5 );
if( key1 == -1 )
{
perror("ftok error ");
return -1;
}
//2.创建或打开一个共享内存
int shm_id = shmget( key1, 4096, IPC_CREAT | IPC_EXCL | 0666 );
if( shm_id == -1 )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
shm_id = shmget( key1, 0, 0 );
}
else
{
perror("shmget error ");
return -1;
}
}
//3.映射
char *p = shmat( shm_id, NULL, 0 );
if( p == NULL )
{
perror("shmat error ");
return -1;
}
//======使用POSIX信号量 对共享资源 进行保护 ==========
sem_unlink( "/space" ); //只需要在写这边 删除一次
sem_unlink( "/data" );
//读 data --> 0
sem_t * data = sem_open( "/data", O_CREAT | O_EXCL, 0666, 0 );
if( data == SEM_FAILED )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
data = sem_open( "/data", 0 );
}
else
{
perror("sem_open data error ");
return -1;
}
}
//写 space --> 1
sem_t * space = sem_open( "/space", O_CREAT | O_EXCL, 0666, 1 );
if( space == SEM_FAILED )
{
if( errno == EEXIST ) //已经存在,就直接打开
{
space = sem_open( "/space", 0 );
}
else
{
perror("sem_open space error ");
return -1;
}
}
char *s = "1234567890";
int i = 0;
while(1)
{
// p操作 写
sem_wait( space );
//临界区
//4.写一个字符到共享内存
*p = *( s + i ); //s[i]
i = ( i+1 ) % 10; //循环写
// v操作 读
sem_post( data );
}
//5.解除映射
shmdt( p );
}
8561

被折叠的 条评论
为什么被折叠?



