共享内存,信号量和线程的建立

 

重点理解Ipc对象:对于ipc对象一般创建两个进程进行相同的操作是为了有相同的key值,能对相同的ipc对象进行操作

1、IPC对象

包含了:共享内存、信号量、消息队列

查看IPC对象的命令:ipcs -a

IPC对象存在于内核,它创建了之后是否还存在内核跟我们进程是否运行没有关系,只跟我们有没有显式删除它有关系

我们可以通过函数的方式去删除它,也可以通过命令的方式删除它。

命令删除的方式是:ipcrm  -M(代表是删除共享内存)  后面的参数是其key值。

  ipcrm  -m(代表是删除共享内存)  后面的参数是其id值。

  ipcrm  -S(代表删除的是信号量)  后面的参数是其key值。

  ipcrm  -s(代表删除的是信号量)  后面的参数是其id值。

扫描二维码关注公众号,回复: 2722549 查看本文章

 

  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. 线程

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;

}

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_41723504/article/details/81516736