一、进程之间的通信。--- 共享内存。
1、什么是共享内存?机制如何?
共享内存也是属于IPC对象,所以在使用之前,必须为共享内存申请key值。
共享内存由于存在于运行内存上,运行内存在linux下所有的进程都可以访问到,结论就是任意的两个进程都可以通过共享内存进行通信。
实现步骤: key值 -> ID号 -> 共享内存地址 -> 数据交换 -> 撤销映射。
2、 共享内存函数接口?
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.99999%
不为NULL-> 用户自己选择地址 0.000001%
shmflg:普通属性 0
返回值:
成功:共享内存起始地址。
失败:(void *)-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 -> 删除共享内存IPC对象
buf:如果是删除,填NULL就行了。
返回值:
成功:0
失败:-1
3、 案例 -- 使用共享内存通信机制,实现jack.c发送数据给rose.c。
rose.c每隔2S就打印一次共享内存的数据。
发送端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
//1、 申请共享内存的key值。
key_t key = ftok(".",10);
//2、 根据key值去申请共享内存ID号。
int shmid = shmget(key,2048,IPC_CREAT|0666);
//3、 根据ID号去运行内存中申请对应的空间。
char *p = (char *)shmat(shmid,NULL,0);
//4、 不断将数据写入到共享内存上。
while(1)
{
fgets(p,2048,stdin);
//5、 如果把"quit"放在共享内存上,则进程退出。
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
return 0;
}
接收端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
//1、 申请共享内存的key值。
key_t key = ftok(".",10);
//2、 根据key值去申请共享内存ID号。
int shmid = shmget(key,2048,IPC_CREAT|0666);
//3、 根据ID号去运行内存中申请对应的空间。
char *p = (char *)shmat(shmid,NULL,0);
//4、 不断地打印共享内存上的数据出来
while(1)
{
printf("from shm:%s",p);
sleep(1);
if(strncmp(p,"quit",4) == 0)
{
break;
}
}
//5. 撤销映射,删除IPC对象
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
结果:
接受端每隔2S就会打印一次共享内存上的数据,即使数据没有修改,也会打印,不会阻塞。
最终目标:
jack -> 输入一次数据 -> 发送一次 -> rose -> 接收一次数据 -> 打印一次
二、处理同步互斥的方式。 --- 信号量。
1、 什么是信号量?
信号量也是属于IPC对象,所以使用信号量之前必须先申请key值。
信号量不是用于进程之间的通信,只是作用与进程之间的通信。
2、学习信号量的函数接口?
1)申请key值。
key = ftok(".",10);
2)根据key值来申请信号量ID号。 -> semget() -> man 2 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: 信号量中元素的个数。
semflg:IPC_CREAT|0666
返回值:
成功:信号量的ID号
失败:-1
3)设置信号量元素的起始值/删除信号量的IPC对象。 -> 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 -> 删除信号量的ID号
...: 如果cmd填SETVAL,那么最后一个参数就是起始值
如果cmd填IPC_RMID,最后一个参数就会忽略。
例如:我想设置空间为1,数据为0,那么怎么做?
semctl(semid,0,SETVAL,1); -> 设置空间起始值为1
semctl(semid,1,SETVAL,0); -> 设置数据起始值为0
如果想删除信号量ID号。
semctl(semid,0,IPC_RMID);
4)如何实现信号量的P/V操作? (p操作(减1操作): 1变成0, v操作(加1操作): 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:进行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
3、案例:
我们要将信号量加入到刚才的代码中,处理同步互斥。
参考: shm+sem/
三、linux最小资源单位。 -- 线程。
1、 进程与线程的区别?
linux --- 社会
进程 --- 人
线程 --- 人的手
线程是一个进程的内部资源。
2、如何开启一个进程? 如何开启一个线程?
./xxxx -> 开启一个进程
xxxx.c
int main(int argc,char *argv[])
{
/* 代表进程的开始 */
xxxx;
/* 进程的运行过程 */
---> 可以使用函数创建很多个线程。
/* 代表进程的结束,就会导致这个进程内部的所有线程都退出 */
return 0;
}
四、线程的函数接口?
线程中所有函数接口都是在线程库中,所以都是查看man手册的第3手册。
1、 如何创建一个子线程? -> pthread_create() -> man 3 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 *fun(void *arg)) arg:传递给子线程例程函数的参数,如果不需要传递,则填NULL。
返回值:
成功:0
失败:非0
注意:
Compile and link with -pthread.
//如果你使用了该函数,则编译时必须链接线程库。
本来: gcc xxx.c -o xxx
现在: gcc xxx.c -o xxx -lpthread
gec@ubuntu:/mnt/hgfs/GZ2057/09 系统编程/05/code$ gcc pthread_create.c -o pthread_create
/tmp/ccl10xXX.o: In function `main':
pthread_create.c:(.text+0x75): undefined reference to `pthread_create' -> 没有链接线程库
collect2: error: ld returned 1 exit status
2、 案例
尝试在进程中创建一个子线程,让主线程做一件事情,让子线程也去做一件事情,看看会不会同时做。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//子线程
void *my_fun(void *arg)
{
//4. 子线程每隔2S打印一句话appletree,一共打印6S。
int i;
for(i=0;i<3;i++)
{
printf("appletree!\n");
sleep(2);
}
}
int main(int argc,char *argv[])
{
//1. 现在还是一个单进程的程序。
printf("singal process!\n");
//2. 创建一个子线程。
// 本来的进程叫做主线程。
pthread_t tid;
pthread_create(&tid,NULL,my_fun,NULL);
//3. 主线程每隔1S打印一句话helloworld,一共打印10S。
int i;
for(i=0;i<10;i++)
{
printf("helloworld!\n");
sleep(1);
}
return 0; //进程退出,都会导致这个进程中所有的线程都会退出。
}
3、 接合线程。 -> 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 *my_fun(void *arg) //arg = (void *)&a
{
printf("a = %d\n",*(int *)arg);
//4. 子线程每隔2S打印一句话appletree,一共打印6S。
int i;
for(i=0;i<10;i++) // 10S
{
printf("helloworld!\n");
sleep(1);
}
}
int main(int argc,char *argv[])
{
//1. 现在还是一个单进程的程序。
printf("singal process!\n");
//2. 创建一个子线程。
// 本来的进程叫做主线程。
pthread_t tid;
int a = 100;
pthread_create(&tid,NULL,my_fun,(void *)&a);
//3. 主线程每隔1S打印一句话helloworld,一共打印10S。
int i;
for(i=0;i<3;i++) //6S
{
printf("appletree!\n");
sleep(2);
}
//4. 接合线程
pthread_join(tid,NULL); //等待子线程退出了,这个函数才返回!
return 0; //进程退出,都会导致这个进程中所有的线程都会退出。
}