重点理解Ipc对象:对于ipc对象一般创建两个进程进行相同的操作是为了有相同的key值,能对相同的ipc对象进行操作
1、IPC对象
包含了:共享内存、信号量、消息队列
查看IPC对象的命令:ipcs -a
IPC对象存在于内核,它创建了之后是否还存在内核跟我们进程是否运行没有关系,只跟我们有没有显式删除它有关系
我们可以通过函数的方式去删除它,也可以通过命令的方式删除它。
命令删除的方式是:ipcrm -M(代表是删除共享内存) 后面的参数是其key值。
ipcrm -m(代表是删除共享内存) 后面的参数是其id值。
ipcrm -S(代表删除的是信号量) 后面的参数是其key值。
ipcrm -s(代表删除的是信号量) 后面的参数是其id值。
ipcrm -Q(代表删除的是消息队列) 后面的参数是其key值。
ipcrm -q(代表删除的是消息队列) 后面的参数是其id值。
需要特别注意的是,信号量用完之后,需要删除它,因为如果没有删除,它的状态是不确定。
共享内存图
2、共享内存:
1)获得key值
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname (which must refer to an existing, accessible file) 这个路径必须是一个存在的、可访问的的文件
proj_id (which must be nonzero) to generate a key_t 必须是一个非零的数字,然后通过这个函数就会产生一个key_t类型的标示符。
返回值:成功就会返回 对应的key值,错误返回-1,并且可以把对应的错误码打印出来看一下是什么错误。
2)申请共享内存
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key: 第一个参数,第一个参数就是我们第一步通过ftok产生的key值,用来标识唯一的消息队列。
size: PAGE_SIZE 4096 对齐的大小,
shmflg: 创建 IPC_CREAT | 权限位 例如: IPC_CREAT | 0664
返回值:如果成功返回的是共享内存的ID,
如果失败返回的是-1
3)将这个共享内存映射到我们的进程空间。
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:我们前面申请到的共享内存的id。
shmaddr:这个参数是,我们需要将这个共享内存映射到我们进程的哪个地址。这里我们都是用NULL
用NULL的意思是让系统自动给我们分配一个映射地址。
shmflg:SHM_RDONLY 表示共享的内存是只读的。
一般我们都用0,表示共享内存可读写。
返回值:返回值就是映射成功后的地址。可以直接通过这个地址去读写内存区域。
如果出错了,返回-1,并且可以将错误码打印出来。
映射好之后,就可以去操作这一片内存区域了。
4)解除内存的映射
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
这个参数是映射后的地址。
返回值:成功返回0
失败返回-1
5)删除这个共享内存。
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//自己定义一个buf,
Struct shmid_ds * buf;
shmid:要操作的共享内存的ID。
cmd:IPC_STAT 获取对象的属性
IPC_SET 设置对象的属性
IPC_RMID 删除对象。
buf:我们要获取或者设置对象的属性,相关的设置就放在这个结构体里面 struct shmid_ds
返回值
成功:返回值为0
失败:返回值为-1
Sem.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>
// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};
// 初始化信号量
int init_sem(int sem_id,int semnu, int value)
{
union semun tmp;
tmp.val = value;//信号量的初始值,有多少可用资源数
//// 控制信号量的相关信息 int semctl(int semid, int sem_num, int cmd, ...);
//SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
//IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
if(semctl(sem_id, semnu, SETVAL, tmp) == -1)//semnu代表是第几个信号量
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}
// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id,int semnu)
{
struct sembuf sbuf;
sbuf.sem_num = semnu; /*序号 标识信号量集中的第几个信号量 // 信号量组中对应的序号,0~sem_nums-1 */
sbuf.sem_op = -1; /*P操作 对信号量的操作 信号量值在一次操作中的改变量 */
sbuf.sem_flg = 0;//SEM_UNDO;//sem_flg 指定IPC_NOWAIT 则semop函数出错返回EAGAIN 如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
// int semop(int semid, struct sembuf semoparray[], size_t numops)
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}
// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id, int semnu)
{
struct sembuf sbuf;
sbuf.sem_num = semnu; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = 0;//SEM_UNDO;
if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}
// 删除信号量集
int del_sem(int sem_id,int semnu)
{
union semun tmp;
if(semctl(sem_id, semnu, IPC_RMID, tmp) == -1)//删除第semnu个信号量
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}
#if 0//主函数屏蔽代码
int main()
{
int sem_id; // 信号量集ID
key_t key;
pid_t pid;
// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}
// 创建信号量集,其中只有一个信号量
//当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 int semget(key_t key, int num_sems, int sem_flags);
if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
{
perror("semget error");
exit(1);
}
// 初始化:初值设为0资源被占用
init_sem(sem_id, 0);
if((pid = fork()) == -1)
perror("Fork Error");
else if(pid == 0) /*子进程*/
{
sleep(2);
printf("Process child: pid=%d\n", getpid());
sem_v(sem_id); /*释放资源*/
}
else /*父进程*/
{
sem_p(sem_id); /*等待资源*/
printf("Process father: pid=%d\n", getpid());
sem_v(sem_id); /*释放资源*/
del_sem(sem_id); /*删除信号量集*/
}
return 0;
}
#endif
Sem.h
#ifndef __SEM__
#define __SEM__
//或者
#ifndef SEM_H
#define SEM_H
int init_sem(int sem_id,int semnu, int value);
int sem_p(int sem_id,int semnu);
int sem_v(int sem_id, int semnu);
int del_sem(int sem_id,int semnu);
#endif
//理解一个运行现象:当你两个程序都执行几遍以后会发现不能执行了,是因为这里没有删除操作,当执行几遍以后,信号量的值会发生改变,再次执行程序的时候会误判成已经进行了初始化,所以没办法初始化,可能会卡在一个p进程上;
解决办法:ipc -a查看ipc对象信息,ipcrm -s 信号量id;删除信号量,这用再执行的话就会重新初始化,恢复打印;
需要特别注意的是,信号量用完之后,需要删除它,因为如果没有删除,它的状态是不确定。
编译命令:
gcc shm_r.c sem.c -o shm_r
gcc shm_w.c sem.c -o shm_w
Shm_r.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sem.h"
#define DATA 0
#define SPACE 1
int main()
{
key_t key;
int shmid,semid;
char *addr;//映射成功后的地址。
key = ftok(".",1);
if(key < 0)
{
perror("ftok error ");
exit(-1);
}
//创建或者获得这个IPC对象,对我们这里来说,这个IPC对象就是共享内存,返回的就是这个共享内存的id,大家的id都是一样的话,指的都是同一个IPC对象,因为key值一样;
//IPC_EXCL这个宏用来判断信号量是否被初始化了,如果初始化了semget函数会返回<0的数,如果返回值大于或者等于0,则进行下面操作初始化;
semid = semget(key,2,IPC_CREAT | IPC_EXCL | 0666);// semid:是创建的信号量的ID
if(semid < 0 )
{
printf("sem exist\n");
semid = semget(key,2,0666);//表明已经初始化了,则通过相同的key值来创建相同的信号量,以便能对同一个信号量进行操作
}
else
{
printf("sem init\n");
init_sem(semid,DATA,0);
init_sem(semid,SPACE,1);
}
shmid = shmget(key,4069, IPC_CREAT | 0666);//申请共享内存
if(shmid < 0)
{
perror("shmget fail");
exit(-1);
}
addr = shmat(shmid,NULL,0);//将这个共享内存映射到我们的进程空间。
if((int)addr == -1)
{
perror("shmat fail");
exit(-1);
}
char *msg="0123456789";
int i=0;
while(1)
{
sem_p(semid,DATA);//DATA信号量的初始值为0,因为刚开始没东西读,会卡在这里,等另外一个进程写操作后,v操作,data信号量值为1,此时p操作后为0,才可以读;
fprintf(stderr,"%c",*addr);//addr映射成功后的地址,把addr里面的内容通过标准出错方式输出来,因为不带缓冲;
sem_v(semid,SPACE);//写操作进程space信号量值为1,有一个资源可以操作,操作完后,变为0,此时若再想写,会变-1,卡在那里,所以不能写,等到这里读完后,space信号量+1,所以值变为1,所以那边又可以写了,所以形成一个,写进程一个,读进程读一个的机制;
}
return 0;
}
Shm_w.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sem.h"
#define DATA 0
#define SPACE 1
int main()
{
key_t key;
int shmid,semid;
char *addr;
key = ftok(".",1);
if(key < 0)
{
perror("ftok error ");
exit(-1);
}
//创建或者获得这个IPC对象,对我们这里来说,这个IPC对象就是共享内存,返回的就是这个共享内存的id,大家的id都是一样的话,指的都是同一个IPC对象
semid = semget(key,2,IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0 )
{
printf("exist\n");
semid = semget(key,2,0666);
}
else
{
printf("init\n");
init_sem(semid,DATA,0);
init_sem(semid,SPACE,1);
}
shmid = shmget(key,4069, IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget fail");
exit(-1);
}
addr = shmat(shmid,NULL,0);
if((int)addr == -1)
{
perror("shmat fail");
exit(-1);
}
char *msg="0123456789";
int i=0;
while(1)
{
// fgets(addr,4069,stdin);
sem_p(semid,SPACE);
memcpy(addr,msg+i,1);
i = (i+1) % 10;//i在0~9间变化
sem_v(semid,DATA);
}
return 0;
}
注意理解p,v操作
Shm_r.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sem.h"
#define DATA 0
#define SPACE 1
int main()
{
key_t key;
int shmid,semid;
char *addr;
key = ftok(".",1);
if(key < 0)
{
perror("ftok error ");
exit(-1);
}
//创建或者获得这个IPC对象,对我们这里来说,这个IPC对象就是共享内存,返回的就是这个共享内存的id,大家的id都是一样的话,指的都是同一个IPC对象
semid = semget(key,2,IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0 )
{
printf("sem exist\n");
semid = semget(key,2,0666);
}
else
{
printf("sem init\n");
init_sem(semid,DATA,0);
init_sem(semid,SPACE,1);
}
shmid = shmget(key,4069, IPC_CREAT | 0666);
if(shmid < 0)
{
perror("shmget fail");
exit(-1);
}
addr = shmat(shmid,NULL,0);
if((int)addr == -1)
{
perror("shmat fail");
exit(-1);
}
char *msg="0123456789";
int i=0;
while(1)
{
// fgets(addr,4069,stdin);
sem_p(semid,DATA);
fprintf(stderr,"%c",*addr);
sem_v(semid,SPACE);
}
return 0;
}
- 线程
1、线程的基本概念
2、如何创建一个线程?
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread:参数类型pthread_t 用来保存线程的id
attr:描述的是线程的属性,如果说不需要修改线程的属性,可以设置为NULL。
void *(*start_routine) (void *): 这个表示的是一个函数指针,就是说我们这个线程执行起来之后,要做的事情,就在函数里面做。
void *arg:函数的参数是一个void *,如果要传参的话,把参数放在这个地方。
返回值:成功返回0
失败返回-1
注意编译的时候要用:gcc pthread.c -o pthread -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
void * routine1(void *arg)
{
printf("这里是线程的打印\n");
return;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,(void *)routine1,NULL);
sleep(3);
return 0;
}