linux系统编程大纲
1. 进程概念、进程诞生与死亡、进程函数接口、进程意义。
2. 进程通信方式,有名管道、无名管道、信号、消息队列、共享内存、信号量。
3. 进程信号集、如何设置阻塞属性。
4. 线程概念,线程与进程区别?线程诞生与死亡,函数接口。
5. 线程同步互斥方式,有名信号量,无名信号量,互斥锁,读写锁,条件变量。
6. 拓展 -> 线程池 -> 同时处理多个任务。
目录
一. IPC对象
1、 什么是IPC对象?
在linux系统,消息队列/共享内存/信号量都是属于IPC对象。例子: 假设创建一条消息队列,等于创建了一个IPC对象。
动作 结果
访问文件 -> 文件描述符
创建消息队列 -> 消息队列ID -> 代表相对应的IPC对象
创建共享内存 -> 共享内存ID
创建信号量 -> 信号量ID
2. 在linux下,如何查看IPC对象的ID号?
gec@ubuntu:~$ ipcs -a
Shared Memory Segments -> 共享内存
key shmid owner perms bytes nattch status
共享内存ID
Semaphore Arrays -> 信号量
key semid owner perms nsems
信号量ID
Message Queues -> 消息队列
key msqid owner perms used-bytes messages
消息队列ID
3. 如何删除IPC对象?
因为IPC对象ID号唯一的,所以可以根据IPC对象ID号删除。
删除共享内存: ipcrm -m 共享内存ID
删除消息队列: ipcrm -q 消息队列ID
删除信号量: ipcrm -s 信号量ID
二. 如何创建IPC对象?
1、 IPC对象ID由key值来决定,也就是key值一致,那么根据key来申请ID号,ID号也会一致。
也就是说申请ID号之前,首先要申请一个key值。
2. 如何申请key值? -> ftok() -> man 3 ftok
功能: 获取IPC对象的key值
convert a pathname and a project identifier to a System V IPC key
提供一个路径和一个数字,那么就会返回IPC对象的key值。
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname:一个合法并且存在的文件 "."
proj_id:非0整数 0~128 例如: 10
只要路径/整数有一个不一致,那么返回值就会不一样。
返回值:
成功:申请到的key值
失败:-1
练习: 验证"只要路径/整数有一个不一致,那么返回值就会不一样"
#include "head.h"
int main()
{
key_t k1,k2,k3,k4;
k1 = ftok(".",10);
k2 = ftok("..",10);
k3 = ftok(".",20);
k4 = ftok(".",10);
printf("k1 = %d\n",k1); //与k4相同
printf("k2 = %d\n",k2); //k2与k1/k3都不相同
printf("k3 = %d\n",k3); //k3与k1/k2都不相同
printf("k4 = %d\n",k4); //与k1相同
}
结论: 如果两个任意的进程需要访问同一个IPC对象,那么这两个进程申请key值必须参数一致!
三、进程之间的通信 -> 消息队列
消息队列特征: 读取特性类型的数据。
管道特性: 有什么数据,只能读取什么数据。
消息队列程序思路
1、 为消息队列IPC对象申请key值 -> ftok()
2、 根据申请到的key值去申请一个新的ID号 -> msgget() -> man 2 msgget
include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key: 申请的key值
msgflg: IPC_CREAT -> 不存在则创建 -> 需要添加起始权限: IPC_CREAT|0666
IPC_EXCL -> 存在则报错
返回值:
成功:消息队列的ID号
失败:-1
3、 往消息队列中写入数据 -> msgsnd() -> man 2 msgsnd
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息队列的ID号
msgp:数据缓冲区 -> 包含类型以及正文
struct msgbuf{ -> 需要在代码中重新声明一个这样的结构体,正文大小由用户自己设定!
long mtype; -> 消息的类型
char mtext[1]; -> 消息的正文 -> 自定义的结构体/数组
};
msgsz:消息正文的大小
msgflg: 正常普通属性填0
返回值:
成功:0
失败:-1
4、 读取消息队列的数据 -> msgrcv() -> man 2 msgrcv
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid:消息队列的ID号
msgp:数据缓冲区 -> 包含类型以及正文
struct msgbuf{ -> 需要在代码中重新声明一个这样的结构体,正文大小由用户自己设定!
long mtype; -> 消息的类型
char mtext[1]; -> 消息的正文 -> 自定义的结构体/数组
};
msgsz:消息正文的大小
msgtyp:需要读取的消息的类型
msgflg: 正常普通属性填0
返回值:
成功: 实际读取到的消息正文的字节数
失败: -1
5、删除消息队列 -> msgctl() -> man 2 msgctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid:消息队列的ID号
cmd: IPC_RMID -> 删除消息队列
buf: 如果是获取属性,则需要填一个指针struct msqid_ds *
如果是删除,则只需要NULL
返回值:
成功:0
失败:-1
例题: 使用消息队列,使得Jack进程可以发送数据给Rose进程。
Rose进程
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
int main(int argc,char *argv[])
{
//1. 为消息队列IPC对象申请key值
key_t key = ftok(".",10);
//2. 根据key值申请消息队列的ID
int msgid = msgget(key,IPC_CREAT|0666);
//3. 从消息队列中读取数据
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
ret = msgrcv(msgid,&gec,sizeof(gec.mtext),10,0);
if(ret == -1)
{
printf("msgrcv error!\n");
exit(-1);
}
printf("from Jack:%s",gec.mtext);
if(strncmp(gec.mtext,"quit",4) == 0)
break;
}
//4. 删除消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
Jack进程
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
int main(int argc,char *argv[])
{
//1. 为消息队列IPC对象申请key值
key_t key = ftok(".",10);
//2. 根据key值申请消息队列的ID
int msgid = msgget(key,IPC_CREAT|0666);
//3. 不断往消息队列中写入数据
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
gec.mtype = 10; //消息的类型
fgets(gec.mtext,50,stdin); //消息的正文 quit
ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
if(ret == -1)
{
printf("msgsnd error!\n");
exit(-1);
}
if(strncmp(gec.mtext,"quit",4) == 0)
{
break;
}
}
return 0;
}
四. 进程之间的通信 -> 共享内存
1、 共享内存的机制?
两个进程根据key与ID号申请一片内存空间,两个进程只需要访问同一片内存空间就可以互相通信。
2、 如何使用共享内存实现两个进程之间通信?
操作思路:
1)为共享内存IPC对象申请key值。 -> ftok()
2)根据key值申请共享内存的ID号。 -> shmget()
3)根据ID号在内存空间中映射出一片内存空间。 -> shmat()
4)根据内存空间的地址进行交互。
5)撤销内存空间。 -> shmdt()
6)删除共享内存对应的IPC对象。 -> shmctl()
3、实现共享内存的函数接口?
1)根据key值申请共享内存的ID号 -> shmget() -> man 2 shmget
功能: 获取共享内存ID allocates a shared memory segment
使用格式:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key:申请的key值
size:必须是PAGE_SIZE倍数 PAGE_SIZE=1024
shmflg:
IPC_CREAT -> 不存在则创建 -> 需要添加起始权限: IPC_CREAT|0666
IPC_EXCL -> 存在则报错
返回值:
成功:共享内存的ID号
失败:-1
2)根据ID号在内存空间中映射出一片内存空间? -> shmat() -> man 2 shmat
功能:产生内存空间
使用格式:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存的ID号
shmaddr: NULL -> 系统分配一片未使用的内存空间。
不为NULL -> 用户自己决定地址
shmflg:普通属性 -> 0
返回值:
成功:共享内存的起始地址
失败:(void *)-1
3)撤销内存空间 -> shmdt() -> man 2 shmdt
功能:撤销映射
使用格式:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr: 需要撤销内存空间起始地址
返回值:
成功:0
失败:-1
4) 删除共享内存对应的IPC对象 -> shmctl() -> man 2 shmctl
使用格式:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存的ID号
cmd: IPC_RMID -> 删除消息队列
buf: 如果是获取属性,则需要填一个指针struct msqid_ds *
如果是删除,则只需要NULL
返回值:
成功:0
失败:-1
练习:使用共享内存实现两个进程之间通信。 Jack -> Rose
读取内存代码:
#include "head.h"
int main()
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 申请ID
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3. 根据ID号申请内存空间
char *p = (char *)shmat(shmid,NULL,0);
//4. 不断获取共享内存空间
while(1)
{
printf("%s",p);
usleep(500000);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
//5. 撤销映射
shmdt(p);
//6. 删除IPC对象
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
写入数据:
#include "head.h"
int main()
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 申请ID
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3. 根据ID号申请内存空间
char *p = (char *)shmat(shmid,NULL,0);
//4. 不断从键盘中获取数据,然后输入内存中
while(1)
{
fgets(p,1024,stdin); //"hello"
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
return 0;
}
结果: 无论共享内存中有没有数据的变化,都会不断打印出来数据出来 -> 数据践踏
有数据变化 -> 打印
无数据变化 -> 阻塞 -> 信号量
五、 信号量
1、 什么是信号量?
进程之间通信方式 -> 有名管道,无名管道,信号,消息队列,共享内存。
信号量不是一种通信方式,它只是作用于通信方式,作用: 处理同步互斥。
2、 关于信号量使用函数接口?
1)根据key值申请ID号 -> semget() -> man 2 semget
功能: get a semaphore set identifier
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key: 信号量IPC对象的key值
nsems: 信号量集合中元素的个数 -> 空间/数据 ->2
semflg: IPC_CREAT|0666 -> 不存在则创建
IPC_EXCL -> 存在则报错
返回值:
成功:信号量ID
失败:-1
2)信号量如何实现P/V操作? -> semop() -> man 2 semop
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid: 信号量ID
sops: 进行P/V操作的结构体的指针
该结构体:
struct sembuf{
unsigned short sem_num; 操作信号量元素的下标 空间->0 数据->1
short sem_op; P操作/V操作 p -> -1 v -> 1
short sem_flg; 普通属性 -> 0
}
nsops: 信号量操作结构体的个数 ->1
返回值:
成功:0
失败:-1
能P/V,马上返回。
不能P/V,只能阻塞。
3)设置信号量属性 -> semctl() -> man 2 semctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
semid:需要操作的信号量的ID号
semnum: 操作信号量元素的下标 空间->0 数据->1
cmd:SETVAL -> 用于设置信号量元素的起始值
IPC_RMID -> 删除信号量
...: 空间/数据起始值,删除填NULL!
例子: 设置有空间 -> semctl(semid,0,SETVAL,1);
返回值:
成功:0
失败:-1
练习: 把信号量加入到共享内存中
发送方:
#include "head.h"
int main()
{
//1. 为共享内存与信号量申请key值
key_t key1 = ftok(".",10); //共享内存
key_t key2 = ftok(".",20); //信号量
//2. 为共享内存与信号量申请ID
int shmid = shmget(key1,1024,IPC_CREAT|0666); //共享内存
int semid = semget(key2,2,IPC_CREAT|0666); //信号量
//3. 根据ID号申请内存空间
char *p = (char *)shmat(shmid,NULL,0);
//4. 设置信号量起始值 写入: 有车位(空间为1),无车(数据为0)
semctl(semid,0,SETVAL,1); //有车位
semctl(semid,1,SETVAL,0); //无车
struct sembuf space;
space.sem_num = 0; //空间
space.sem_op = -1; //P操作
space.sem_flg = 0;
struct sembuf data;
data.sem_num = 1; //数据
data.sem_op = 1; //v操作
data.sem_flg = 0;
//5. 不断从键盘中获取数据,然后输入内存中
while(1)
{
//请问车位能否减1,能 -> 开车进内存空间 不能 -> 阻塞等待
semop(semid,&space,1); //想空间P操作 -> 能:返回 不能:阻塞
fgets(p,1024,stdin); // 把车开进内存空间
//想车的数量+1
semop(semid,&data,1);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
return 0;
}
接收方:
#include "head.h"
int main()
{
//1. 为共享内存与信号量申请key值
key_t key1 = ftok(".",10); //共享内存
key_t key2 = ftok(".",20); //信号量
//2. 为共享内存与信号量申请ID
int shmid = shmget(key1,1024,IPC_CREAT|0666); //共享内存
int semid = semget(key2,2,IPC_CREAT|0666); //信号量
//3. 根据ID号申请内存空间
char *p = (char *)shmat(shmid,NULL,0);
//4. 设置信号量起始值 写入: 有车位(空间为1),无车(数据为0)
semctl(semid,0,SETVAL,1); //有车位
semctl(semid,1,SETVAL,0); //无车
struct sembuf data;
data.sem_num = 1; //数据
data.sem_op = -1; //p操作
data.sem_flg = 0;
struct sembuf space;
space.sem_num = 0; //空间
space.sem_op = 1; //v操作
space.sem_flg = 0;
//4. 不断获取共享内存空间
while(1)
{
//请问有没有车可以开出来?(数据能不能减1) 能: 返回 不能: 阻塞
semop(semid,&data,1); //资源数-1
printf("%s",p); //能,说明数据能减1,马上开走
semop(semid,&space,1); //资源数-1
// usleep(500000);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
//5. 撤销映射
shmdt(p);
//6. 删除IPC对象
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
综合题:
jack:
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
#define J2R 10
#define R2J 20
void fun(int sig)
{
printf("catch sig = %d\n",sig);
exit(0); //父进程发送了quit给Rose
}
int main()
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值申请消息队列ID
int msgid = msgget(key,IPC_CREAT|0666);
//3. 带着这条队列,产生一个子进程
pid_t x;
x = fork();
if(x > 0)
{
signal(SIGCHLD,fun);
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
gec.mtype = J2R;
fgets(gec.mtext,50,stdin);
ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
if(ret == -1)
{
printf("msgsnd error!\n");
exit(-1);
}
if(strncmp(gec.mtext,"quit",4) == 0) //Jack发送了quit给Rose,那么Jack的子进程也应该要退出!
{
kill(x,SIGUSR1);
exit(0); //正常退出
}
}
}
if(x == 0)
{
signal(SIGUSR1,fun);
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
ret = msgrcv(msgid,&gec,sizeof(gec.mtext),R2J,0);
if(ret == -1)
{
printf("msgrcv error!\n");
exit(-1);
}
printf("from Rose :%s",gec.mtext);
if(strncmp(gec.mtext,"quit",4) == 0) //子进程如果收到了quit,就不要发送数据给父进程了
{
msgctl(msgid,IPC_RMID,NULL);
exit(0); //正常退出 -> 自动会发送SIGCHLD给父进程
}
}
}
}
rose:
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
#define J2R 10
#define R2J 20
void fun(int sig)
{
//printf("catch sig = %d\n",sig);
exit(0); //父进程发送了quit给Rose
}
int main()
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值申请消息队列ID
int msgid = msgget(key,IPC_CREAT|0666);
//3. 带着这条队列,产生一个子进程
pid_t x;
x = fork();
if(x > 0)
{
signal(SIGCHLD,fun);
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
gec.mtype = R2J;
fgets(gec.mtext,50,stdin);
ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
if(ret == -1)
{
//printf("msgsnd error!\n");
exit(-1);
}
if(strncmp(gec.mtext,"quit",4) == 0) //Jack发送了quit给Rose,那么Jack的子进程也应该要退出!
{
kill(x,SIGUSR1);
exit(0); //正常退出
}
}
}
if(x == 0)
{
signal(SIGUSR1,fun);
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
ret = msgrcv(msgid,&gec,sizeof(gec.mtext),J2R,0);
if(ret == -1)
{
//printf("msgrcv error!\n");
exit(-1);
}
printf("from Jack :%s",gec.mtext);
if(strncmp(gec.mtext,"quit",4) == 0) //子进程如果收到了quit,就不要发送数据给父进程了
{
msgctl(msgid,IPC_RMID,NULL);
exit(0); //正常退出 -> 自动会发送SIGCHLD给父进程
}
}
}
}