第5章 锁与进程间通信(2)

目录

5.3 System V进程间通信

5.3.1 System V 机制

5.3.2 信号量

5.3.3 消息队列

5.3.4 共享内存


本专栏文章将有70篇左右,欢迎+关注,查看后续文章。

5.3 System V进程间通信

即 AT&T System V机制。

System V Release 4:即SVR4,最成功的版本。

进程间通信:即IPC。

3种System V的IPC机制:

        信号量,消息队列,共享内存。

5.3.1 System V 机制

3种IPC实例在内核都有一个IPC对象。

内核中用magic唯一标识IPC对象,并且可用于获得IPC对象。

一个IPC对象拥有的属性:

        1. 用户ID,组ID。

        2. 访问权限(读写)。

5.3.2 信号量

应用层使用。

编程实例:

        #define         SEM_MAGIC         1234L         //magic,IPC标识符

        #define         PERMS                 0666         //访问权限

        struct sembuf         op_down[1]  =  {0, -1, 0};

        struct sembuf         op_up[1]  =  {0, 1, 0};

        int semid   =   semget(SEM_MAGIC, 0, IPC_CREAT | PERMS)

                可测试信号量是否已创建。semid在内核标识了信号量。

        semctl(semid,   0,   SETVAL,  1);                 //初始化值为1

        semop(semid,   &op_down[0],   1);         // down操作

        semop(semid,   &op_up[0],   1);                 // up操作

内核实现

进程所属IPC命名空间:

        task_struct -> nsproxy -> ipc_ns

默认的IPC命名空间:

        struct ipc_namespace init_ipc_ns;

struct   ipc_namespace {         //包括:IPC统计信息,IPC资源限制信息。

        ...

        struct ipc_ids         ids[3];

                //ids[0]:信号量 ids[1]:消息队列 ids[2]:共享内存

        ...

}

IPC资源限制,如:

        用于共享内存的页最大数目。

        共享内存段的最大长度。

        消息队列的最大数目。

struct         ipc_ids {         //表示信号量,或消息队列,或共享内存

        int                                     in_use; //该类型的IPC对象数目。

        unsigned short                 seq;

                //每当创建一个新IPC时,seq递增,标识唯一IPC对象。

        struct rw_semaphore         rwsem;

                //内核信号量,用于实现用户空间信号量。

        struct idr                              ipcs_idr;

                // ID基数树,

        int                                         next_id;

                //存储下一个待分配的IPC ID,简化了ID生成

};

struct idr         ipcs_idr;

        作用:一个基数树,管理IPC ID与其对应的kern_ipc_perm。

        idr:ID Radix Tree,基数树。高效分配、查找、删除和遍历整数 ID。

struct kern_ipc_perm         *perm    =   idr_find(&ids->ipcs_idr,   next_id);

        以IPC ID为索引,从基数树中查找kern_ipc_perm对象。

每个IPC ID对象有一个kern_ipc_perm实例。

struct   kern_ipc_perm {         //表示该IPC对象的权限。

        spinlock_t           lock;

        bool                    deleted;

        int                       id;            //内核中IPC ID。

        key_t                  key;         //应用层标识IPC的magic。

        kuid_t                 uid;         //所有者用户ID。

        kgid_t                 gid;         //组ID。

        kuid_t                 cuid;       //产生IPC的进程用户ID。

        kgid_t                 cgid;       //产生的IPC的进程组ID 。

        umode_t             mode;    //位掩码,表示访问权限。

        unsigned long         seq;         //序号,分配IPC时使用。

        void                         *security;

}

task_struct中包含了IPC信号量信息。

struct    task_struct {

        ...

        struct sysv_sem     sysvsem;         //信号量

}

struct        sysv_sem {

        struct sem_undo_list         *undo_list;

}

        undo_list:用于撤销信号量。

        场景:修改信号量的进程崩溃,等待信号量的其他进程无法被唤醒,防止死锁。

                不详细展开。

struct         sem_array {         //管理一组信号量struct sem的属性。

        struct kern_ipc_perm        sem_perm;           //权限。

        time_t        sem_otime;         //最后一次操作信号量的时间。

        time_t        sem_ctime; //最后一次修改信号量的时间。

        struct sem                 *sem_base;         //指向信号量集合中第一个信号。

        struct list_head         sem_pending;      //保存待决的操作的struct sem_queue。

        struct list_head         list_id;                   //该集合中的撤销请求。

        int         sem_nsems;         //该集合中信号量数目。

        int         complex_count;

};

struct        sem {        //内核中表示一个信号量。

        int                                semval;    //当前值

        int                                sempid    //上一次操作该信号量的进程PID

        struct list_head            sem_pending;

                //信号量的待决队列,每个进程通过struct sem_queue -> list 添加到该队列。

}

struct    sem_queue:

        每个进程有一个该实例,将该实例添加到信号量的待决队列中。

        作用:关联了信号量与睡眠进程。

struct    sem_queue {

        struct list_head             list;                 //用于将进程加入到信号量的待决队列。

        struct task_struct         *sleeper;         //等待信号量的进程。

        struct sem_undo         *undo;             //用于撤销。

        int         pid;            //请求信号量的进程。

        int         status;       //操作的完成状态。

        struct sembuf         *sops;    //对信号量的操作。

        int         nsops;       //操作数目。

        int         alter;         //操作是否改变了数组。

};

struct         sembuf {         //对应应用层对信号量的操作。

        unsigned short         sem_num;    //操作的信号量集合中索引。

        short         sem_op;    //操作。如减3,则sem_op=-3。

        short         sem_flg;  //操作标识。

};

实现系统调用

sys_semctl:操作信号量值

sys_semget:读取信号量ID

权限检查

int      ipcperms(struct ipc_namespace    *ns,     struct kern_ipc_perm    *ipcp,    short    flag)

        检查是否有权限操作IPC对象。

5.3.3 消息队列

一个进程向队列发送消息,而其他进程从队列中接收消息。

每个消息格式:消息正文+编号。

        编号的作用:标识一个消息。

一个进程读取消息后,内核将从队列中将消息删除。

所以每个消息只被一个进程读取。

同一编号消息:按先进先出处理。

ipc_ids[1] ---> ID号 --->(基数树)--> kern_ipc_perm

struct msg_queue{

        time_t         q_stime;    //上一次调用msgsnd的时间。

        time_t         q_rtime;    //上一次调用msgrcv的时间。

        time_t         q_ctime;    //上一次修改队列属性的时间。

        pid_t           q_lspid;     //上一次调用msgsnd的pid

        pid_t           q_lrpid;      //上一次调用msgrcv的pid

        unsigned long         q_cbytes;     //队列中当前字节数。

        unsigned long         q_qnum;      //队列中消息数目。

        unsigned long         q_qbytes;     //队列中最大字节数。

        struct list_head         q_messages;    //连接所有消息。

        struct list_head         q_receivers;     //连接消息的接收进程。

        struct list_head         q_senders;      //连接消息的发送进程。

}

q_messages:

        作用:连接所有消息,其中每个消息用struct msg_msg表示。

struct         msg_msg {         //一个消息

        struct list_head         m_list;   //连接其他消息。

        long              m_type;    //消息类型

        int                 m_ts;       //消息正文长度。

        struct msg_msgseg         *next;   //若消息超过一页,用next连接

}

消息读取后,从队列中删除, 所以每个消息只能由一个进程读取。

若消息队列已满,发消息的进程将睡眠。

若消息队列为空,接收消息的进程将睡眠。

struct        msg_sender {

        struct list_head            list;     //将进程连接到msg_queue的q_senders链表中

        struct task_struct         *tsk;   //发送消息的进程。

}

struct         msg_receiver {

        struct list_head         r_list;      //将进程连接到msg_queue的q_receivers链表中

        struct task_struct         *r_tsk;   //接收消息的进程

        int            r_mode;

        long         r_msgtype;

        long         r_maxsize;

        struct msg_msg *volatile         r_msg;

}

5.3.4 共享内存

struct         shmid_kernel {

        ...

        struct kern_ipc_perm         shm_perm;

        struct file                         *shm_file;   //每个共享内存都创建一个伪文件

}

通过shm_file->f_mapping 访问 struct address_space。即可创建、读写对应匿名映射的VMA。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山下小童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值