一、进程之间的通信方式 --- 共享内存。
1、共享内存也是属于IPC对象,所以在使用共享内存之前一定要申请key值与ID号。
2、共享内存作用机制以及使用范围。
作用范围:由于共享内存是IPC对象,属于系统的资源,所以在整个系统中都可以看到IPC对象,其作用范围就是linux下任意的两个进程。
机制:任意一个进程可以将数据发送到共享内存上,另外的进程就可以访问内存上的数据。
3、实现共享内存的步骤:
1)先申请key值
key = ftok(".",10);
2)根据申请到的key值去申请共享内存的ID号。 --> shmget() --> man 2 shmget
功能: allocates a System V 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的倍数 #define PAGE_SIZE 1024
shmflg: IPC_CREAT|0666 --> 不存在则创建
返回值:
成功:共享内存ID号
失败:-1
3)根据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 --> 系统为你分配空间地址 99.99%
不为NULL --> 由用户自己去分配 0.01%
shmflg: 普通属性 -> 0
返回值:
成功:共享内存的地址
失败:-1
4)如何撤销空间? --> shmdt() --> man 2 shmdt
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
shmaddr:共享内存的地址
返回值:
成功:0
失败:-1
5)删除共享内存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:如果是删除,则设置为NULL
返回值:
成功:0
失败:-1
例题: 使用共享内存机制,来实现进程之间的通信。
写端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值去申请共享内存的ID号
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3. 根据ID号去共享内存中申请空间
char *p = shmat(shmid,NULL,0);
if(p == (void *)-1)
printf("shmat error!\n");
//4. 不断往共享内存中写入数据
while(1)
{
fgets(p,1024,stdin);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
return 0;
}
读端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值去申请共享内存的ID号
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3. 根据ID号去共享内存中申请空间
char *p = shmat(shmid,NULL,0);
if(p == (void *)-1)
printf("shmat error!\n");
//4. 每隔2秒就打印一次
while(1)
{
//sleep(2);
printf("from shm:%s",p);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
//5. 撤销映射
shmdt(p);
//6. 删除共享内存IPC对象
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
运行结果:
如果写端将某一个数据放置到共享内存上,那么读端就会一直读取共享内存的数据,不会阻塞。 --> 数据践踏。
我们想要的目标:
发送一次 --> 打印一次
再发送一次 --> 再打印一次
二、处理共享内存互斥关系 --- 信号量。
1、什么是信号量?
信号量不是进程之间的通信方式,只是作用于进程之间的通信。
信号量通过管理空间+数据,实现进程对同一片共享内存的访问互斥关系。
信号量由于是IPC对象,所以必须要申请key值与ID号。
2、关于信号量函数接口?
1)先申请key值。
key_t key = ftok(".",10);
2)根据key值去申请信号量的ID号。 --> semget() --> man 2 semget
功能: semget - get a System V semaphore set identifier
//获取信号量的ID号
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数:
key: key值
nsems: 信号量中元素的个数 空间+数据 -> 2
semflg: IPC_CREAT|0666 --> 不存在则创建
返回值:
成功:信号量的ID号
失败:-1
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:IPC_RMID --> 用于删除信号量
SETVAL --> 用于设置信号量的起始值
...:如果第三个参数填SETVAL,那么就要填最后一个参数,这个参数就是起始值。
例如:我想设置空间为1,数据为0,代码如何写?
semctl(semid,0,SETVAL,1); //空间为1
semctl(semid,1,SETVAL,0); //数据为0
返回值:
成功:0
失败:-1
4)如何实现信号量的p/v操作? (p操作:1->0 v操作:0->1) --> semop() ---> man 2 semop
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
semid: 信号量ID号
sops:
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操作,代码如何写?
struct sembuf space; //定义结构体变量
space.sem_num = 0; //代表空间
space.sem_op = -1; //代表p操作
space.sem_flg = 0;
semop(semid,&space,1); //空间p操作
例如:我想数据v操作,代码如何写?
struct sembuf data;
data.sem_num = 1; //代表数据
data.sem_op = 1; //代表v操作
data.sem_flg = 0;
semop(semid,&data,1); //数据v操作
例题: 已知shm/目录下的代码运行结果会出现数据践踏,请把信号量技术加入到shm/目录下的代码中,把数据践踏这个问题处理掉。
读端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/sem.h>
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key1 = ftok(".",10);
key_t key2 = ftok(".",20);
//2. 根据key值去申请共享内存和信号量的ID号
int shmid = shmget(key1,1024,IPC_CREAT|0666);
int semid = semget(key2,2,IPC_CREAT|0666);
//3. 根据ID号去共享内存中申请空间
char *p = shmat(shmid,NULL,0);
if(p == (void *)-1)
printf("shmat error!\n");
//4 设置信号量的起始值
semctl(semid,0,SETVAL,1); //设置空间为1
semctl(semid,1,SETVAL,0); //设置数据为0
//5. 数据p操作
struct sembuf data;
data.sem_num = 1; //数据
data.sem_op = -1; //p操作
data.sem_flg = 0; //普通属性
//6. 空间v操作
struct sembuf space;
space.sem_num = 0; //空间
space.sem_op = 1; //v操作
space.sem_flg = 0; //普通属性
//7. 每隔2秒就打印一次
while(1)
{
//请问数据能不能减1? //数据p操作
semop(semid,&data,1);
printf("from shm:%s",p); //开车出来
//车位的数量(空间)自动加1
semop(semid,&space,1);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
//8. 撤销映射
shmdt(p);
//9. 删除共享内存IPC对象
shmctl(shmid,IPC_RMID,NULL);
//10. 删除信号量
semctl(semid,0,IPC_RMID);
return 0;
}
写端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <sys/sem.h>
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key1 = ftok(".",10);
key_t key2 = ftok(".",20);
//2. 根据key值去申请共享内存和信号量的ID号
int shmid = shmget(key1,1024,IPC_CREAT|0666);
int semid = semget(key2,2,IPC_CREAT|0666);
//3. 根据ID号去共享内存中申请空间
char *p = shmat(shmid,NULL,0);
if(p == (void *)-1)
printf("shmat error!\n");
//4 设置信号量的起始值
semctl(semid,0,SETVAL,1); //设置空间为1
semctl(semid,1,SETVAL,0); //设置数据为0
//5. 空间p操作
struct sembuf space;
space.sem_num = 0; //空间
space.sem_op = -1; //p操作
space.sem_flg = 0; //普通属性
//6. 数据v操作
struct sembuf data;
data.sem_num = 1; //数据
data.sem_op = 1; //v操作
data.sem_flg = 0; //普通属性
//7. 不断往共享内存中写入数据
while(1)
{
//请问空间能不能减1?
semop(semid,&space,1); //空间p操作
fgets(p,1024,stdin); //开车进去
//车的数量(数据)自动加1。 //数据v操作
semop(semid,&data,1); //数据v操作
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
return 0;
}
三、linux下最小资源单位。 -- 线程。
1、进程与线程的区别?
假设linux是一个社会,那么进程就是一个人,线程就是一个人的一只手。
线程其实就是一个进程的内部资源。
int main()
{
/* 进程开始 */
xxxxx;
xxxxx; --> 在进程运行过程中,可以开启很多个线程。
xxxxx;
xxxxx;
return 0;
/* 进程结束 */
}
2、线程函数接口特点?
1)由于线程函数都是封装在线程库中,所以我们是看不到源码,查看线程函数的使用格式,都是在第3手册: man 3 xxx
2)所有线程函数头文件只有一个:#include <pthread.h>
四、线程的函数接口?
1、如何在一个进程中创建一个线程? --> pthread_create() --> man 3 pthread_create
功能: pthread_create - create a new thread
//创建一个新的线程
使用格式:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
thread: 存储线程ID号的空间的地址。
attr: 线程的属性,普通属性填NULL。
start_routine: 线程的例程函数 这个函数必须长: void *func(void *arg)
(你想线程做什么事情,只需要把这些事情写入到这个函数中即可)
arg:传递给线程例程函数的参数,如果不需要传递,则设置为NULL
返回值:
成功:0
失败:非0错误码
注意: Compile and link with -pthread.
//编译时要链接线程库
错误示范:gcc xxx.c -o xxx
正确示范:gcc xxx.c -o xxx -lpthread
大概:
void *func(void *arg) //子线程的例程函数
{
//线程开始
?????? --> 主线程与子线程的代码是同时运行的。
??????
??????
//线程结束
}
int main()
{
//进程开始
pthread_create(xxx , xxx , func , xxx);
//主线程
?????? --> 主线程与子线程的代码是同时运行的。
??????
??????
return 0; //进程结束
}
例题: 尝试去创建一个子线程,看看能不能同时做两件事情。
//情况一: 子线程与主线程一起工作,子线程工作时间短,主线程工作时间长。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *func(void *arg) //线程的工作函数 arg = NULL
{
//现在这里就是子线程
int i;
for(i=0;i<5;i++)
{
sleep(1);
printf("child i = %d\n",i);
}
}
int main(int argc,char *argv[])
{
//1. 马上创建一个子线程。
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);
//2. 现在这里就是主线程
int i;
for(i=0;i<10;i++)
{
sleep(1);
printf("parent i = %d\n",i);
}
return 0;
}
运行结果:
子线程运行5s 主线程运行10s 程序正常退出。
//情况二: 子线程与主线程一起工作,子线程工作时间长,主线程工作时间短。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *func(void *arg) //线程的工作函数 arg = NULL
{
//现在这里就是子线程
int i;
for(i=0;i<10;i++)
{
sleep(1);
printf("child i = %d\n",i);
}
}
int main(int argc,char *argv[])
{
//1. 马上创建一个子线程。
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);
//2. 现在这里就是主线程
int i;
for(i=0;i<5;i++)
{
sleep(1);
printf("parent i = %d\n",i);
}
return 0;
}
运行结果:
主线程与子线程在5s时,一起结束了。
2、如何接合一个线程? --> pthread_join() --> man 3 pthread_join
功能: pthread_join - join with a terminated thread
//接合一个结束的线程
使用格式:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
thread:需要接合的那个子线程的ID号
retval:存储子线程退出值的指针,如果不关心子线程的退出状态只接合,那么就设置为NULL。
返回值:
成功:0
失败:非0错误码------------------------------------------------
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *func(void *arg) //线程的工作函数 arg = NULL
{
//现在这里就是子线程
int i;
for(i=0;i<10;i++)
{
sleep(1);
printf("child i = %d\n",i);
}
}
int main(int argc,char *argv[])
{
//1. 马上创建一个子线程。
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);
//2. 现在这里就是主线程
int i;
for(i=0;i<5;i++)
{
sleep(1);
printf("parent i = %d\n",i);
}
//3. 原地等待孩子的退出,接合他。
pthread_join(tid,NULL);
return 0;
}
----------------------------------------------
3、线程的退出? --> pthread_exit() --> man 3 pthread_exit
功能: pthread_exit - terminate calling thread
//结束一个正在运行的线程
使用格式:
#include <pthread.h>
void pthread_exit(void *retval);
参数:
retval: 存储着退出值的变量的地址 --> 这个退出值必须是全局变量,不能是局部变量。
返回值:无--------------------------------------------------
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int exit_state = 50;
void *func(void *arg)
{
int i;
for(i=0;i<5;i++)
{
printf("%d\n",i);
sleep(1);
}
//如果你想让主线程知道,你是怎么退出的,那么就得调用pthread_exit()函数。
pthread_exit((void *)&exit_state);
}
int main(int argc,char *argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);
void *p = NULL;
pthread_join(tid,&p); //p = (void *)&exit_state
printf("exit_state = %d\n",*(int *)p);
return 0;
}
---------------------------------------------------