当前位置: 代码迷 >> 综合 >> 进程之间的通信(共享内存)&&处理同步互斥的方式(信号量)&&linux最小资源单位(线程)&&线程的函数接口
  详细解决方案

进程之间的通信(共享内存)&&处理同步互斥的方式(信号量)&&linux最小资源单位(线程)&&线程的函数接口

热度:23   发布时间:2024-02-22 10:24:05.0

一、进程之间的通信。--- 共享内存。
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; //进程退出,都会导致这个进程中所有的线程都会退出。
}