- 概念引入
- 为什么要信号量?
- 信号量操作接口
- 1.申请信号量semget
- 2.控制信号量semctl
- 3.处理信号量semop
- IPC资源的组织方式
概念引入
信号量是什么?
本质是一个计数器,通常用来表示公共资源中,资源数量多少的问题
公共资源:能被多个进程同时可以访问的资源
访问没有保护的公共资源:数据不一致问题(比如我想写abc123,但是我123还没有写入,就读取了abc,可能数据分开会导致数据无意义)
为什么要让不同的进程看到同一份资源呢?因为我想通信,进程间实现协同。但是进程具有独立性,没有办法让两个进程直接通信,为了解决这种问题,解决办法就是让进程看到同一份资源,但是因为提出了这个方法,同时也引入了新的问题:数据不一致
我们将保护起来的公共资源称为:临界资源(大部分资源是独立的)
资源包括内存、文件、网络等,资源存在的意义就是要被使用,如何被进程使用呢?
一定是该进程有对应的代码来访问这部分临界资源(有临界区就有非临界区)
多进程在进行进程间通信时本质是要看到一份公共的资源,这部分公共资源在未来如果被保护起来,那么这部分公共资源我们就称其为临界资源,而访问该临界资源中我们自己的那部分代码我们叫做临界区,不访问临界资源那部分代码的我们叫做非临界区
如何保护资源:互斥与同步
原子性:要么不做,要做就做完,只有两种状态(不会被线程调度机制打断的操作)
为什么要信号量?
举例认识:比如我们看电影,我们不是只要坐在一个座位上,那么这个座位就属于你了,而是你要去买票,然后给你分配一个座位,这个座位才属于你,不管你去不去,你只要买票了,那么这个座位始终都会留给你,这种电影买票的本质:对放映厅中的座位进行预定机制。我们想要某种资源的时候也可以进行预定。
共享资源的使用:作为一个整体使用、划分为一个一个的子资源使用
我们想让不同的进程同时访问一份资源的不同区域,这样我们某种程度上能实现并发
假如我们将一份共享资源(电影院)分成几份,进程(人),访问共享资源的子部分(电影的座位),我们不能直接去访问这些资源(不能不买票随便坐),信号量(电影票),每个进程先申请信号量,如果申请成功了,就相当于预定这部分资源(买到票有座位),申请失败就不允许访问这部分资源(没买票到不允许看电影)。这种申请信号量方式以达到保护共享资源,约束其他进程的目的。信号量就是计数器(电影票是有限的),申请一个就少一个。
sem = 20;//定义信号量总数
sem--; //预定资源
//访问公共资源
sem++; //释放资源
PV操作:P → 预定资源 V → 释放资源
所有的进程在访问公共资源之前,都必须先申请sem信号量→必须先申请sem信号量的前提,是所有进程必须先得看到同一个信号量→信号量本身就是公共资源→信号量是不是也要保证自己的安全呢?
- -,++
→信号量必须保证自身操作的安全性,- -,++
操作是原子! !
如果一个信号量初始值为1:二元信号量–互斥功能
思考:有没有可能两个进程都申请信号量成功了却访问同一个资源?
有的,申请信号量成功只能表明这个资源一定有部分是留给你访问的,但具体是哪一部分还需要确认。这部分的工作一般是由程序员来指定的
信号量操作接口
1.申请信号量semget
函数原型:
int semget(key_t key, int nsems, int semflg);
函数参数:
key
:一个用于标识信号量集的键值,它必须是一个非零的整数。通常使用 ftok 函数来生成键值。nsems
:信号量集中包含的信号量的数量。这个参数必须是一个大于零的整数。semflg
:一个标志参数,用于指定信号量集的创建和访问权限。常见的标志包括:
IPC_CREAT
:如果指定的 key 值对应的信号量集不存在,则创建它。
IPC_EXCL
:如果同时指定了 IPC_CREAT 和 IPC_EXCL 标志,而对应的信号量集已经存在,则会出现错误。
0666
:指定新创建的信号量集的权限。这个参数使用八进制表示,表示该信号量集可以被所有用户读写。
函数返回值:
semget 函数的返回值是一个信号量集的标识符,也称为“信号量集描述符”。它是一个非负整数,可以用于后续的信号量操作函数中。
2.控制信号量semctl
函数原型:
int semctl(int semid, int semnum, int cmd, ...);
函数参数:
-
semid
:一个信号量集的标识符,它是由 semget 函数返回的。 -
semnum
:要操作的信号量在信号量集中的编号,从 0 开始计数。 -
cmd
:用于指定要执行的操作,常见的操作包括:
GETVAL
:获取指定信号量的当前值。
SETVAL
:设置指定信号量的值。
IPC_RMID
:删除指定的信号量集。
IPC_STAT
:获取指定信号量集的状态信息。
IPC_SET
:设置指定信号量集的状态信息。 -
...
:可选参数,用于给 SETVAL 命令指定要设置的值。
函数返回值根据不同的命令而异:
- 对于
GETVAL
命令,返回指定信号量的当前值。 - 对于
SETVAL
命令,返回 0 表示操作成功,-1 表示操作失败。 - 对于
IPC_RMID、IPC_STAT 和 IPC_SET
命令,返回 0 表示操作成功,-1 表示操作失败。
3.处理信号量semop
int semop(int semid, struct sembuf *sops, unsigned nsops);
函数参数:
semid
:一个信号量集的标识符,它是由 semget 函数返回的。sops
:一个指向sembuf
结构体数组的指针,每个结构体描述一个操作。sembuf 结构体定义如下:
struct sembuf {
unsigned short sem_num; // 操作的信号量在信号量集中的编号
short sem_op; // 操作的值
short sem_flg; // 操作的标志
};
- `sem_num`:要操作的信号量在信号量集中的编号,从 0 开始计数。
- `sem_op`:要执行的操作,可以是一个正数、负数或零。
- 如果 `sem_op` 是一个正数,表示对应的信号量的值将增加 `sem_op`。
- 如果 `sem_op` 是一个负数,表示对应的信号量的值将减少 `sem_op`。
- 如果 `sem_op` 是零,表示对应的信号量的值不变。
- `sem_flg`:操作的标志,包括:
- `SEM_UNDO`:表示在进程异常终止时,内核将撤销该进程所做的所有操作。
- `IPC_NOWAIT`:表示如果无法立即执行相应的操作,则立即返回。
nsops
:sops 数组中元素的数量。
函数返回值:
表示是否操作成功,如果成功则返回 0,如果失败则返回 -1。
IPC资源的组织方式
共享内存、消息队列、信号量它们的接口相似度很高,比如获取与删除
且它们开始都是定义:
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
对其进行操作:
(struct shmid_ds*)perms[0]->其他的属性
struct shmid_di myshm;
perms[0] = &myshm.shm_perm;
struct msqid_ds mymsg;
perms[1] = &mymsg.msg_perm;
结构体的第一个成员的地址,在数字上和结构体对象本身的地址数字是相等的,我们就可以通过结构体的地址,强转struct shmid_ds*
来使用其里面的其他属性,如上述伪代码。
如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀