进程与线程

目录

1.linux多任务机制

1.1.并发和并行执行

2.进程

2.1进程是什么

3.进程相关命令

3.1 获取进程PID

3.2 进程说明

3.3 进程状态之间的转换

3.4创建子进程

1.fork函数

4.结束进程

1.exit()函数

5.孤儿进程与僵尸进程

1.孤儿进程

2.僵尸进程

6.回收进程资源:wait族函数

1.wait函数

2.waitpid函数

7.exec族函数

8.线程

1.什么是线程

2.如何创建线程

2.1示例:

2.2注意事项

3.给线程传递参数

4.链接方式

4.1静态链接

4.2动态链接

5.静态库和共享库

9.pthread_join函数:线程资源回收

10.结束线程

11.线程的互斥

1.解决互斥问题

1.1使用互斥锁来解决问题

2.初始化互斥锁:

3.加锁:

4.解锁:

12.线程的同步

1.解决同步问题

2.信号量的使用过程:

1.创建信号量

2.初始化信号量

3.请求信号量

4.释放信号量

13.信号量解决互斥问题

14. 信号量解决同步问题

1.解决两个线程的同步

2.解决三个线程的同步

15.进程间通信方式

1.无名管道

1.1创建无名管道

2.有名管道

2.1创建有名管道

2.2有名管道的特点

3.信号

3.1信号的响应方式

3.2接收信号

3.3捕获信号

3.4忽略信号

3.5执行默认操作

3.6发送信号

1.kill函数

2.raise函数

3.alarm函数

4.SIGCHLD信号

4.共享内存

1.如何使用共享内存

2.共享内存的特点

3.查看和删除共享内存

5.消息队列

1.消息队列的特点

2.查看和删除消息队列

3.如何使用消息队列

16.设置线程属性

1. 测试线程堆栈大小

2.修改线程堆栈大小

1.pthread_attr_init函数

3 案例代码


1.linux多任务机制

(1)多任务处理 用户可以在同一时间内运行多个应用程序,每一个正在执行的应用程序被称为一个任务。 (2) 任务的执行 一次任务的执行,可以迸发激活多个进程,这些进程相互配合完成这个终极目标 (3)多任务执行的本质--->时间片轮转机制。

单核的处理器在某一时刻只能执行一个任务。每个任务创建时会被分配时间片,任务执行(占用时间片),时间片递减,操作系统的时间片用完时调度执行其他任务,频繁切换并且时间片非常短,所以给用户的感觉是同一时刻在运行多个任务。

1.1.并发和并行执行

并发执行:单核cpu 不能实现真正意义上的同时运行多个应用程序,通过时间片快速的频繁切换给人的感觉是同时运行。 并行执行:多核cpu 能实现真正意义上同一时刻运行多个应用程序。

2.进程

2.1进程是什么

//什么是进程? (1)进程是一个程序的一次“动态”的执行过程 vi hello.c //每隔1s打印一次helloworld gcc hello.c //a.out ./a.out --->程序运行起来时 (2)进程是一个独立的可被调用的任务 //每当运行进程(./a.out)时系统会分配4G虚拟内存 (3)进程是操作系统进行资源分配和调度的基本单位。

“进程是资源分配的最小单位,线程是CPU调度的最小单位”

当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源;

每个进程创建的时候都会给它分配四个区的资源,于此相对于的线程,就只会给它一个区也就是栈区的资源。

所以总的来说:

进程是资源管理的最小单位 (内存管理)

线程是什么的最小单位(资源调度)

(4)进程和程序的区别 程序是一段静态的代码,是保存在计算机的存储器上指令和数据有序的集合,没有任何执行的概念;---》死的 进程是一个动态的概念,它是程序的一次执行过程,包括动态的创建、调度、执行和消亡的整个过程,它是程序执行的和资源管理的基本单位。

(5)进程的特性 并发性 动态性 交互性 独立性 (6)进程分类 交互式进程 批处理进程 守护进程

3.进程相关命令

(1)查看所有进程相关信息 process进程   ps aux 
(2) 动态显示进程的相关信息   top
(3) 列出进程关系树     pstree   
(4) 杀死进程命令     kill pid     pid进程的唯一标识

3.1 获取进程PID

(1)头文件及函数原型
#include <sys/types.h>
#include <unistd.h>
(2)函数原型
    pid_t getpid(void);
    //等价写法 int getpid(); 因为pid_t就是重命名的int类型
    功能:获取当前进程pid    
    pid_t getppid(void);
    //parent 父 process进程
    //等价写法 int getppid()
    功能:获取当前进程父进程PID
(3) 返回值 --返回当前的进程的pid
    pid_t //类型是什么?? 
   typedef int pid_t;
   

3.2 进程说明

1. 进程PID
    每运行一次系统会分配一个唯一标识PID,当前进程结束时PID会被回收再利用,但是不会立刻分配出去。
2. 进程与终端
    运行在终端上的所以进程父进程都是终端,依附于终端,终端要是结束了,运行在终端上的所有进程也就消亡。
3. 查看当前Linux系统PID的最大值 P46
    cat /proc/sys/kernel/pid_max
    
    
    32位操作系统 pid最大值是32768 
                pid = 1的进程是init,操作系统最先被调用的进程。

3.3 进程状态之间的转换

进程的三个基本状态 (1)就绪态 (2)运行态 (3)阻塞态

3.4创建子进程

1.fork函数
(1)头文件及函数原型    
#include <unistd.h>
pid_t fork(void);
//等价写法
(2)功能 :创建子进程---》使用fork函数会在./a.out中创建一个新的进程。新创建的进程我们称为子进程,原来的进程称为父进程。 当使用fork创建子进程时,子进程会几乎复制父进程的所有东西。包括堆区,栈区,全局区,代码段等等。但是pid是复制不了的。
    子进程会从fork的下一行开始执行。
    (3)返回值:fork函数的返回值用于区分父子进程
    ret == -1时 创建子进程失败
    ret == 0时 代表是子进程
    ret > 0时 代表是父进程
    
  
    fork()函数是Unix/Linux系统中的一个系统调用,用于创建一个新的进程,新进程是调用进程(父进程)的一个副本。它的作用包括以下几个方面:
​
    1.复制父进程: fork()函数会复制父进程的地址空间(包括代码段、数据段、堆和栈等)到子进程中。这样,子进程就可以运行和父进程相同的程序代码和数据。
    2.设置进程ID: 在创建子进程时,操作系统会为子进程分配一个新的进程ID(PID)。父进程和子进程有不同的PID,从而可以唯一标识每个进程。
    3.设置父子关系: 子进程的父进程ID(PPID)被设置为调用fork()函数的父进程的PID,这样可以建立父子进程之间的关系。
    4.返回值: 在调用进程(父进程)中,fork()函数返回两次。第一次返回时,返回新创建的子进程的PID;第二次返回时,在子进程中返回0。这样可以通过返回值来区分父进程和子进程,实现不同的逻辑。
​
    总的来说,fork()函数的主要作用是创建一个新的进程,新进程与父进程共享代码和数据,但具有独立的执行环境和内存空间,从而实现了进程的复制和并发执行。
    
    
    实际上,子进程从fork()函数返回的地方开始执行,而不是从fork()函数调用之后的下一行语句开始执行。这是因为fork()函数是在父进程中调用并返回两次的,在子进程中返回0,在父进程返回的是子进程的PID。因此,子进程会从fork()函数返回的位置开始执行。
   

4.结束进程

1.exit()函数

功能:用于结束当前进程。退出进程前会刷新缓存区。

需要引用头文件:

#include<stdlib.h>
原型:

void exit(int status);
参数:

status-----》状态

exit(0)------》正常退出

exit(-1)------》异常退出,一般写-1,只要非0就行

19.2_exit()函数
功能:用于结束当前进程。但是退出进程前不会刷新缓存区。

需要引用头文件:

#include <unistd.h>
原型:

 void _exit(int status);
参数:

status-----》状态

_exit(0)------》正常退出

_exit(-1)------》异常退出,一般写-1,只要非0就行

1.exit和_exit函数相同点,调用之后都会将进程结束,参数都可以用来标识进程结束时状态
2.exit和_exit函数不同点,exit结束进程之前会刷新缓存区,_exit不会刷新缓存区直接结束。

5.孤儿进程与僵尸进程

1.孤儿进程

孤儿进程:父进程先结束,但是子进程未结束,此时的子进程就被称为孤儿进程,会被init (pid为1的进程)所接管。

2.僵尸进程

僵尸进程:子进程结束了,父进程未结束,但父进程却没有调用wait族函数来对子进程收尸(回收资源),此时子进程被称为僵尸进程。 僵尸进程是坏事,要避免,父进程晚于子进程结束,并在子进程结束时为它收尸(回收资源)

6.回收进程资源:wait族函数

正常情况应该父进程晚于子进程结束,而且子进程结束时父进程应该调用wait族函数进行回收资源。   wait函数:等待子进程结束,结束时回收资源  
 waitpid函数:等待指定进程结束,结束时回收资源

1.wait函数

功能:回收子进程资源。wait阻塞等待子进程结束,一旦有子进程结束,立刻解除阻塞为子进程回收资源。wait不指定哪一个,只要是当前进程创建的子进程结束,立刻回收资源

需要引用头文件:

 #include <sys/types.h>
 #include <sys/wait.h>
原型:

 pid_t wait(int *status);
参数说明:

int *status----可以通过地址传递的形式,获取子进程结束时的状态。通常设置为NULL,忽略子进程结束时的状态。

返回值:

成功:pid_t----》让父进程结束阻塞的子进程的pid。(即结束的子进程的pid)

失败:-1

2.waitpid函数

功能:指定等待某个子进程的结束,等到该子进程结束后,回收资源。

需要引用头文件:

 #include <sys/types.h>
 #include <sys/wait.h>
原型:

 pid_t  waitpid(pid_t  pid,  int  *status,   int
       options);
参数说明:

pid_t  pid //指定等待的子进程的pid
int  *status   //子进程结束时的状态
int options //选项,可选择是否阻塞等待。 0代表阻塞等待,WNOHANG 代表非阻塞等待
返回值:

>0--->子进程结束了,也就是子进程的pid
0:WNOHANG的时候,等待的子进程未结束
-1:出错
示例:

waitpid(5188,NULL,0);//阻塞等待5188进程结束,并且回收资源

7.exec族函数

功能: 用fork函数创建完子进程,子进程中往往需要调用exec族函数中一员,实现执行另一个程序。 当进程调用exec族中的函数时,该进程就会完全被新程序所取代,而新程序从main函数开始执行。 因为exec族函数并不创建新的进程,所以pid没变还是原来的,只是替换了堆区、栈区...代码段等等。 只要执行了exec族函数,程序相当于大换血--》五脏六腑都被新程序替换了。所以一般都是在子进程中使用exec族函数,子进程无所谓,但是主进程有所谓。

需要引用头文件:

#include <unistd.h>
函数原型:

这个族的函数就比较多了

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...); //...表示多个参数
int execle(const char *path, const char *arg,
          ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
          char *const envp[]);
          
参数说明:

l //list 缩写 希望接收以逗号分隔的参数列表,列表以NULL作为结尾标志
p //PATH 系统会按照PATH环境变量进行路径进行查找命令          
e //enviroment 缩写 指定当前进程所使用的环境变量
v //vertor 缩写 参数传递为以NULL结尾的字符指针数组
    
返回值:若出错则为- 1,若成功则不返回

8.线程

1.什么是线程

1、线程是轻量级的进程

2、线程都要依附于进程,一个进程可以创建多个线程,如果一个进程结束了,进程创建的所有线程都会随之消亡

3、创建线程只开辟一个栈区,多个线程之间共享进程的资源

4、多个线程之间是并发执行,没有固定的先后顺序,具体执行顺序有系统调用度和资源分配略所决定

线程在程序中是独立的,因此它也是最小的独立运行单位

2.如何创建线程

功能:创建一个线程

需要包含头文件:

#include <pthread.h>
原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
参数说明:

pthread_t *thread //线程的唯一标识 线程的id
const pthread_attr_t *attr //设置线程的属性通常赋值为NULL
void *(*start_routine) (void *) //线程的执行函数
void *arg //要不要给第三个参数线程执行函数传递参数
void *(*start_routine) (void *) //函数指针
指针的类型是什么??  
void *(*) (void *)
          指针指向的类型是什么?? 
void *  (void *)
//线程执行函数
void* fun(void* p)
{
                //该线程要做的操作
}
返回值:

成功: 0 失败:-1
2.1示例:
#include "my.h"
//fun函数就是线程要执行的功能函数
//类型必须是void * (void *)
void *fun(void *p)
{
    while(1)
    {
        printf("hello world!!\n");//一定要加\n
        sleep(1);
    }
    return NULL;
}
​
int main(int argc, const char *argv[])
{
    pthread_t id;//用来保存线程的ID
    int ret = pthread_create(&id, NULL, fun, NULL);
    if(ret == -1)
    {
        perror("pthread_create failed");
        exit(-1);
    }
    printf("11111111111111111\n");
//  sleep(3);
    getchar();//阻塞键盘输入一个字符,目录是为了让进程一直活着,线程才活着
    return 0;
}
2.2注意事项

注意编译的时候,需要加上 -lpthread gcc hello.c -lpthread //动态去加载 libpthread.so要加载的是这个动态库

Windows下动态库后缀 .dll linux下动态库后缀 .so结尾的 linux下静态库后缀 .a结尾的

3.给线程传递参数

#include "my.h"
​
typedef struct
{
    char name[20];
    int age;
}stu_t;
​
//线程1 传递整型
void *thread1(void *p)//void *p = &age;
{
    //方法二 将无类型的指针赋值给有类型的指针,再使用
    int *q = p;
    while(1)
    {
        //方法一 将无类型指针p强制类型转换为有类型的指针,再使用
        printf("方法一:age is %d\n",*(int*)p);
        printf("方法二:age is %d\n",*q);
        sleep(1);
    }
}
//线程2 传递字符串
void *thread2(void *p)
{
    char *q = p;
    while(1)
    {
        printf("方法一:name is %s\n",(char*)p);
        printf("方法二:name is %s\n",q);
        sleep(1);
    }
}
//线程3 传递结构体
void *thread3(void *p)
{
    stu_t * q = p;
    while(1)
    {
        printf("方法一:name is %s  age is %d\n", ((stu_t*)p)->name, ((stu_t*)p)->age);
        printf("方法二:name is %s  age is %d\n", q->name, q->age);
        sleep(1);
    }
}
​
int main(int argc, const char *argv[])
{
    stu_t s = { 0 };
    int age = 19;
    char name[20] = "asan";
    //给结构体成员变量赋值
    strcpy(s.name, name);
    s.age = age;
    pthread_t id1, id2, id3;
    pthread_create(&id1, NULL, thread1, &age);//将age传递给线程1,在线程1中将age打印
    pthread_create(&id2, NULL, thread2, name);//将字符串传递给线程2,在线程2中将name打印
    pthread_create(&id3, NULL, thread3, &s);//将结构体传递给线程3,在线程3中将age和name都打印
    pthread_join(id1, NULL);//阻塞等待线程1结束,当线程1结束,才会解除阻塞
    pthread_join(id2, NULL);
    pthread_join(id3, NULL);
    return 0;
}
ps:线程执行时,最后写的线程三最先执行,线程一最后执行

4.链接方式

4.1静态链接

静态链接模型: 编译时去静态库找函数,将函数源码编译程序中

4.2动态链接

动态链接模型:编译时去动态库找函数地址,将函数地址编译到程序中

5.静态库和共享库

静态库:是以.a结尾的库文件,在编译的时候去静态库函数源码,将函数源码编译到程序中,因此体积大,但在运行的时候不需要再加载静态库。 动态库:是以.so结尾的库文件,也叫共享库。在编译的时候去动态库中找函数地址,将函数地址编译到程序中,因此体积小,但在运行的时候需要根据地址去动态库加载函数。

9.pthread_join函数:线程资源回收

功能:阻塞等待线程结束并回收资源

头文件:
#include <pthread.h>
函数原型: int pthread_join(pthread_t thread, void **retval);    
参数说明:
    pthread_t thread //线程id 阻塞等待的子进程编号
    void **retval    //用来保存线程结束时的返回值的 通常赋值为NULL
       // void* fun(void*p ) 返回值void*
返回值:
    
    成功: 0
    失败: -1
 //调用参考
  pthread_join(id,NULL);
    

10.结束线程

功能:结束当前线程         
#include <pthread.h> void pthread_exit(void *retval);  
 pthread_exit(NULL);

11.线程的互斥

一个进程可以有多个线程,这些线程共享进程的资源,在使用资源的时候会其他线程带来一些影响。也就是抢夺资源,当一个线程拥有某个资源时,其他线程想要访问这个资源就会陷入阻塞,等待该资源被释放。

1.解决互斥问题

1.1使用互斥锁来解决问题

任何线程都要遵守在访问共享资源时先加锁,如果加锁成功则使用共享资源,如果加锁失败,阻塞等待。再使用完共享资源时,必须解锁。

互斥锁的使用方法:

(1)创建互斥锁
    pthread_mutex_t mutex;//放全局区写
(2)初始化互斥锁
    pthread_mutex_init(&mutex,NULL);//放main中写
(3)加锁
    pthread_mutex_lock(&mutex);//放每个线程中写使用共享资源前
(4)解锁
    pthread_mutex_unlock(&mutex);//放每个线程中写使用完共享资源
2.初始化互斥锁:
需要引用的头文件:

#include <pthread.h>
函数原型:

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *restrict attr);
参数说明:

pthread_mutex_t *mutex //创建的互斥锁
const pthread_mutexattr_t *restrict attr //设置互斥锁的属性,通常赋值为NULL
返回值: 成功:返回0 失败:返回错误编号
3.加锁:
需要引用的头文件:

#include <pthread.h>
函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex);
参数说明:

pthread_mutex_t *mutex   //创建的那个互斥锁
返回值: 成功: 0 失败:返回错误编号
4.解锁:

12.线程的同步

同步:多线程之间要按照一定顺序相互配合完成某项任务

1.解决同步问题

可以使用信号量来解决同步问题。

可以将信号量理解为一类资源(停车场 教室)

可以使用信号量来解决互斥问题 也可以使用信号量来解决同步问题

2.信号量的使用过程:

1.创建信号量
sem_t sem;
2.初始化信号量

需要引用的头文件:

#include <semaphore.h>

函数原型:

 int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

sem_t *sem //创建的信号量 int pshared //process share 0代表多线程要共享信号量 unsigned int value    //值为1时代表初始化一个共享资源类似一把互斥锁用来解决互斥问题    //值为0时代表初始化为0个共享资源,用来解决同步问题

返回值:成功: 返回 0 失败: 返回 -1

3.请求信号量

功能:请求信号量

需要引用头文件:

#include <semaphore.h>

函数原型:

#include <semaphore.h>

参数说明: sem_t *sem //信号量

返回值: 成功: 返回 0 失败: 返回 -1

4.释放信号量

需要引用的头文件:

#include <semaphore.h>

函数原型:

 int sem_post(sem_t *sem);

参数说明: sem_t *sem

返回值: 成功: 返回 0 失败: 返回 -1

13.信号量解决互斥问题

 信号量(停车场)停车位的数量为1,解决互斥
        sem_t sem = { 0 };
        sem_init(&sem,0,1);//第三个参数是1 停车位数1 类似于互斥锁,用来解决多线程之间共享资源互斥问题
​
#include "my.h"
​
int max = 200;//全局变量,是一个共享资源,线程1和2都可以直接使用全局变量
​
sem_t sem;//创建一个信号量,创建一个停车场,本质上就是定义一个结构体变量
​
void *thread1(void *p)
{
    while(1)
    {
​
        //请求信号量,请求成功,停车位-1,解除阻塞,访问共享资源max,请求失败,阻塞等待
        sem_wait(&sem);
        max = 50;
        sleep(2);//延时2s,cpu不可能等2s
        printf("max is %d\n",max);
        //释放信号量 停车位+1
        sem_post(&sem);
    }
}  
void *thread2(void *p)
{
    while(1)
    {
        //请求信号量,请求成功,停车位-1,解除阻塞,访问共享资源max,请求失败,阻塞等待
        sem_wait(&sem);
        max = 300;
        //释放信号量 停车位+1
        sem_post(&sem);
    }
}
​
int main(int argc, const char *argv[])
{
    pthread_t id1, id2;
    //初始化信号量,初始化停车场
    sem_init(&sem, 0, 1);//第三个参数1,代表初始化1个停车位
    pthread_create(&id1, NULL, thread1, NULL);
    pthread_create(&id2, NULL, thread2, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    return 0;
}

14. 信号量解决同步问题

同步:多个线程之间要按照一定顺序去执行,多个线程之间相互配合,完成某项任务。 
线程1: 生产啤酒瓶 
线程2: 装啤酒 
线程3: 盖盖子    
 1 --->  2  --->  3  ---> 1  --->  2  ---> 3 ........ sem_init(&sem,0,0);//第三个参数初始化为0 用来解决线程同步问题 ​ 
因为初始时共享资源数量是0,线程1不请求信息号,线程2请求信号量,线程2阻塞等待。 线程1执行完操作要释放一个信号量,这个时候线程2才会解除阻塞(唤醒)
  

1.解决两个线程的同步

#include "my.h"
sem_t sem;//创建一个信号量,创建一个停车场,本质上就是定义一个结构体变量
void *thread1(void *p)
{
    while(1)
    {
        printf("生产啤酒瓶子!!\n");
        sem_post(&sem);//释放信号量,停车位+1,唤醒线程2
        sleep(1);//延时1s,每隔1s生产一个啤酒瓶子,唤醒线程2一次
    }
}  
void *thread2(void *p)
{
    while(1)
    {
        sem_wait(&sem);//第一次请求失败,因为停车场初始化停车位是0个, 阻塞等待被唤醒
        printf("装啤酒!!!\n");
    }
}
int main(int argc, const char *argv[])
{
    pthread_t id1, id2;
    //初始化信号量,初始化停车场
    sem_init(&sem, 0, 0);//第三个参数0,代表初始化0个停车位
    pthread_create(&id1, NULL, thread1, NULL);
    pthread_create(&id2, NULL, thread2, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    return 0;
}

2.解决三个线程的同步

#include "my.h"
sem_t sem;//创建一个信号量,创建一个停车场,本质上就是定义一个结构体变量
sem_t sem2;
void *thread1(void *p)
{
    while(1)
    {
        printf("生产啤酒瓶子!!\n");
        sem_post(&sem);//释放信号量,停车位+1,唤醒线程2
        sleep(1);//延时1s,每隔1s生产一个啤酒瓶子,唤醒线程2一次
    }
}  
void *thread2(void *p)
{
    while(1)
    {
        sem_wait(&sem);//请求失败,阻塞等待被唤醒
        printf("装啤酒!!!\n");
        //唤醒线程3
        sem_post(&sem2);
    }
}
void *thread3(void *p)
{
    while(1)
    {
        //阻塞等待线程2的唤醒
        sem_wait(&sem2);
        printf("盖盖子!!\n");
    }
}
​
int main(int argc, const char *argv[])
{
    pthread_t id1, id2, id3;
    //初始化信号量,初始化停车场
    sem_init(&sem, 0, 0);//第三个参数0,代表初始化0个停车位
    sem_init(&sem2, 0, 0);
    pthread_create(&id1, NULL, thread1, NULL);
    pthread_create(&id2, NULL, thread2, NULL);
    pthread_create(&id3, NULL, thread3, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    pthread_join(id3, NULL);
    return 0;
}

15.进程间通信方式

  1. 信号(signal)

  2. 消息队列 (message queue)

  3. 共享内存 (shared memory)

  4. 信号量 (semaphore)

  5. 套接字 (socket)//网络编程 前5种 ---在一台电脑上不同进程间能通信 第6种 ---不同主机之间不同进程间通信

1.无名管道

  1. 无名管道只能在有亲缘关系的进程中使用

  2. 无名管道是单工通信(半双工),固定读端和写端 fd[0]读端描述符 fd[1]写端描述符

  3. 无名管道不属于任何文件系统,存在内存中(内核空间)

  4. 无名管道中无数据的时候,读取数据read阻塞等待

  5. 无名管道在写入数据的时候 ,先进先出特点

  6. 无名管道在文件系统中不可见,只能通过pipe()获得到读端写端描述符,可以用read write读写。

  7. 不支持lseek移动文件指针的操作

1.1创建无名管道
//pipe 无名管道
(1)功能:创建无名管道 并获得到读端和写端的文件描述
(2)头文件及函数原型     
    #include <unistd.h>
    int pipe(int pipefd[2]); //int pipe(int * pipefd)
​
(3) 参数说明: 
      int pipefd[2];//获取读端写端文件描述符
      pipefd[0] --->读端文件描述符
      pipefd[1] --->写端文件描述符
    
(4)返回值 
    成功: 0
    失败:-1
    
    //调用
    read(pipefd[0],buf,sizeof(buf));
    write(pipefd[1],buf,strlen(buf));

2.有名管道

2.1创建有名管道
1. 头文件及函数原型  
    #include <sys/types.h>
    #include <sys/stat.h>   
    int mkfifo(const char *pathname, mode_t mode)
    //make mk   first in first out fifo
2. 功能:
    创建有名管道
​
3. 参数说明:
    const char *pathname //要创建的有名管道的名字可以加路径修饰
    mode_t mode //权限 新创建的有名管道文件的权限
chmod
4. 返回值 
    成功: 0
    失败: -1
        
       //调用
        mkfifo("./myfifo" ,0666);//相对路径
        mkfifo("/home/linux/24076/myfifo" ,0666);//绝对路径
2.2有名管道的特点
1. 有名管道在有亲缘关系和无亲缘关系进程之间都可以使用 
2. 有名管道是单工通信(半双工通信),固定的读端和写端 
3. 有名管道不属于任何文件系统,存在于内存中(内核空间) 
4. 有名管道中无数据的时候,读取数据read阻塞等待 
5. 有名管道在读写数据的时候 FIFO,先进先出 特点 
6. 有名管道名字,在文件系统中可见,就可以调用open函数打开,得到文件描述符,进行read write 
7. 不支持lseek移动文件指针的操作

3.信号

signal 信号是进程间通信机制中唯一的异步通信机制。

(1)信号:    异步:一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。 
(2)信号的使用 通常一个进程发送给另一个进程 
(3)信号发出者 系统 进程 
(4)信号接收者 进程 
(5)关于ctrl + c   ctrl + c组合键被终端识别,终端(进程)要给运行在终端上的进程发送SIGINT信号,这个信号的默认操作是终止进程。 
(6)如何查看系统有哪些信号: kill -l      
 #define SIGINT 2   
#define SIGQUIT 3         
SIGINT ctrl+c  终止程序//2     
SIGQUIT ctrl+\  终止程序 // 3    
SIGILL 非法指令    
SIGABRT 通过abort函数实现程序终止,abort函数为异常终止一个进程    
SIGFPE 除数为0 会产生  --浮点异常    
SIGKILL 必杀信号 //9   kill -9 pid  
SIGSEGV   段错误--地址非法使用    
SIGPIPE   管道破裂    S
IGALRM   闹钟信号 用alarm函数设置闹钟 告诉系统时间到 当时间到 就会发送这个信号 //14    
SIGTERM   终止信号 kill命令向进程发送这个信号 //15      
SIGCHLD    子进程终止 或者停止的时候 会向父进程发送此信号 //17    
SIGCONT   让一个暂停的进程继续    
SIGSTOP     让一个程序暂停    
SIGTSTP     ctrl+z 将前台进程扔到后台 并使该进程处于暂停状态 //20
3.1信号的响应方式

当进程接收到某个信号后,有三种响应的方式 (1) 忽略信号 //即对信号不做任何处理 (2) 捕捉信号 //定义信号处理函数,当信号发生时,执行相应的处理函数 (3) 执行默认操作 //linux对每种信号都规定了默认操作

3.2接收信号
(1)功能: signal函数能接收到其他进程发送给来的信息 ,并做出响应。
(2) 函数原型
   //既是指针函数还是回调函数 参数返回值都是函数指针
   void (*signal(int signum, void (*handler)(int)))(int);
    //函数名  signal    
    //参数列表 函数名挨着的()里就是参数
    //返回值  挡着函数名和参数列表剩下就是返回值类型
    函数名字叫什么??   signal
    函数的参数有哪些?? int signum, void (*handler)(int)
    函数的返回值类型是什么??   void (*)(int);函数指针
     void (*)(int) signal(int signum, void (*handler)(int)) ;
    //对指针类型重定义
     //将void (*)(int)类型重定义为handler_t
        typedef void (*)(int) handler_t;
                    ||
         typedef void (*handler_t )(int) ;
    //signal函数简写方式  
    handler_t signal(int signum,handler_t p);
(3)参数: 
        int signum //接收到信号编号
        void (*handler)(int) //信号处理器  函数指针void (*)(int) 能指向的函数void fun(int p)
         handler//处理器  
(4)返回值 是一个函数指针--返回的是原来的信号处理器 返回NULL
    
     
    //调用 以SIGQUIT为例,写出响应该信号的不同方式的具体实现
    //1. 忽略响应方式
        signal(SIGQUIT,SIG_IGN);//ignore忽略
    //2. 执行默认操作响应方式
        signal(SIGQUIT,SIG_DFL);//default默认----》写于不写没有任何区别
    //3. 捕捉信号后,去做的哪件事,执行fun函数
        void fun(int num)
        {
            
        }
        signal(SIGQUIT,fun);
3.3捕获信号

接收到信号之后,去执行一个信号的处理函数,信号处理函数是一个自定义的函数

#include "my.h"
​
//信号处理函数的类型应该是 void (int),因为函数指针指向的函数类型是void (int)
void fun(int num)
{
    printf("捕捉到信号一次!!!!\n");
    printf("num is %d\n",num);//num里面保存捕捉到的那个信号的编号
}
​
int main(int argc, const char *argv[])
{
    //signal接收信号,接收到信号之后,去执行一个信号处理函数fun
    signal(SIGINT, fun);//ctrl + c  2
    signal(SIGTSTP,fun);//ctrl + z; 20
    signal(SIGTERM,fun);//kill PID  15
    while(1)
    {
        printf("开心的打麻将!!\n");
        sleep(1);
    }
    return 0;
}
3.4忽略信号

接收到信号之后,对该信号进行忽略 SIG_IGN (ignore 忽略)

#include "my.h"
​
int main(int argc, const char *argv[])
{
    //signal接收信号,如果收到SIGINT响应的方式是忽略
    //SIG_IGN 忽略信号的意思
    signal(SIGINT, SIG_IGN);//ctrl + c
    signal(SIGQUIT,SIG_IGN);//ctrl + \;
    signal(SIGTERM,SIG_IGN);//kill PID
    while(1)
    {
        printf("开心的打麻将!!\n");
        sleep(1);
    }
    return 0;
}
3.5执行默认操作

接收到信号之后,对该信号执行默认的操作(缺省操作) SIG_DFL (defalut)

#include "my.h"
​
int main(int argc, const char *argv[])
{
    //signal接收信号,如果收到SIGINT响应的方式是执行默认操作
    //SIG_DFL //defalut 默认 缺省
    signal(SIGINT, SIG_DFL);//ctrl + c
    signal(SIGQUIT,SIG_DFL);//ctrl + \;
    signal(SIGTERM,SIG_DFL);//kill PID
    while(1)
    {
        printf("开心的打麻将!!\n");
        sleep(1);
    }
    return 0;
}
3.6发送信号
1.kill函数
(1)功能:
    给任意进程发送任意信号
(2)头文件及函数原型             
    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int sig);     
(3)参数说明:
    pid_t pid //要给发送那个进程的PID
    int sig //要发送的信号编号
(4)返回值: 
    成功: 0
    失败: -1     
    //调用
     kill(3111,SIGINT)//给PID是3111的进程,发送一个SIGINT信号
     kill(2365,SIGQUIT)//给PID是2365的进程,发送一个SIGQUIT信号
2.raise函数
(1)功能:给当前进程自己发送任意信号    
(2)头文件及函数原型             
    #include <sys/types.h>
    #include <signal.h>
    int raise(int sig);
(3)参数说明:
    int sig //要发送的信号编号      
(4)返回值: 
    成功: 0
    失败: -1    
    //调用
    raise(SIGINT);  //给当前进程自己,发送一个SIGINT信号
    raise(SIGQUIT); //给当前进程自己,发送一个SIGQUIT信号
3.alarm函数
(1)功能:
     闹钟函数,它就是给当前进程中设置了一个定时器,定的时间到了就会发送一个SIGALRM信号
(2) 函数原型
    
    unsigned int alarm(unsigned int seconds)
​
​
(3) 参数:
    
    unsigned int seconds //秒数,每隔多久产生一次信号
          
    alarm(1);  //1s之后自动产生一个SIGALRM信号 
    alarm(4);  //4s之后自动产生一个SIGALRM信号    
    
    
(4) 返回值: 
    
    成功:  如果调用此alarm()前,进程中已经设置了闹钟时间,则
          返回上一个闹钟时间的剩余时间,否则返回0。
    失败: -1
4.SIGCHLD信号
SIGCHLD信号什么时候产生???   //signal child    
当子进程结束时,会自动向父进程发送SIGCHLD信号,但是父进程对该信号的默认操作忽视。    
 利用该信号SIGCHLD信号可以避免僵尸进程了,当子进程结束时发送信号给父进程,父进程接收信号并处理    
为子进程回收资源。    

4.共享内存

为了在多个进程间交换信息,内核专门流出一块内存区,两个或多个进程可以直接读写这一内存空间而不需进行数据的复制,从而大大提高了效率。

共享内存是进程通信机制中效率最高的通信方式。

1.如何使用共享内存

流程如下:

1.创建共享内存 (共享内存ID)

(1)头文件及函数原型   
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>   
    int shmget(key_t key, int size, int shmflg);
    shmget//shared memory get
(2)功能: 创建共享内存
(3)参数:
    private//私有的
    key // 秘钥
    #define IPC_PRIVATE 0
    key //创建共享内存的key值,如果key的值是IPC_PRIVATE,意味着共享内存是私有的,只能在有亲缘进程间使用。如果key为非0的值,意味着共享内存不是私有的,就可以被多个不相关进程访问。
     //多个进程要使用同一块共享内存才可以,要用key值来保证,多个进程创建共享内存时要使用同一个key值,key值是几不重要,一样就行。  
    size //创建的共享内存空间的大小
    shmflg //创建共享内存的访问权限 0666          
 (4)返回值:
    成功:创建出来的共享内存的ID 唯一标识
    失败:-1       
     //调用参考
     //有亲缘关系进程间使用 key值用 IPC_PRIVATE
     int shmid = shmget(IPC_PRIVATE,sizeof(int),0666);
    //无亲缘关系进程间使用 key值用 非0值一样就行
     int shmid = shmget(5555,sizeof(int),IPC_CREAT | 0666);//进程A中
     int shmid = shmget(5555,sizeof(int),IPC_CREAT | 0666);//进程B中

2.将共享内存的地址映射到自己进程中

//映射共享内存,就是为了得到共享内存的首地址
(1)头文件及函数原型     
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);            
(2)功能:将共享内存的地址要映射到自己内存空间中
(3)参数:
    1)shmid //共享内存的id shmget的返回值
    2)shmaddr //要指定映射的地址 通常给NULL
    3)shmflg //对共享内存的访问权限 值是0代表可读可写
(4)返回值:
     //调用参考
    //创建共享内存
    int shmid = shmget(IPC_PRIVATE,sizeof(int),0666);
    //映射
     int* p = (int*)shmat(shmid,NULL,0);//将共享内存映射到自己的空间获取首地址 NULL不指定位置0可读可写

3.读写共享内存

a.向共享内存写入内容   *p  = 100 
b.将共享内存中的内容直接读取打印   printf("%d \n",*p); 
c. 将共享内存中的内容拷贝出来再打印   int num = *p;   printf("%d \n",num);

4.解除映射

(1)头文件及函数原型 
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>        
    int shmdt(const void *shmaddr);
​
(2)功能:将自己进程空间指针和共享内存空间首地址断联系,解除映射
(3)参数:
    shmaddr //映射到自己空间的首地址
   
(4)返回值:
    
    成功 0
    失败 -1           
     //调用参考
     int* p = (int*)shmat(shmid,NULL,0);//将共享内存映射到自己的空间获取首地址 NULL不指定位置0可读可写
    //解除共享内存映射  
    shmdt(p);

5.删除共享内存

(1)头文件及函数原型
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>    
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shared memory control//共享内存控制器
(2)功能:功能有三个 能设置共享内存的属性,还能获取共享内存的属性,当然还能删除共享内存   
(3)参数:      
        int shmid: //共享内存的编号 shmget的返回值
        int cmd:  //要选择的功能
         IPC_SET//设置共享内存的属性
         IPC_STAT//获取共享内存的属性       
         IPC_RMID//删除共享内存
          //调用
          shmctl(shmid,IPC_RMID,NULL);            
         buf:就是用要设置属性或是获取属性时内容存放地 删除共享内存时 值给NULL            
(4)返回值:
        成功 0
        失败 -1        
2.共享内存的特点
  1. 进程间通信效率最高的方式 share memory

  2. 使用的函数名称 (1)shmget() (2)shmat(); (3)读/写 (4)shmdt(); (5)shmctl();

3.查看和删除共享内存

ipcs -m //查看共享内存信息 ipcrm -m 786442 //删除ID是786442的共享内存

5.消息队列

可以发送和接收多个消息,这些消息可以在消息队列中进行缓存(没取走,还在消息队列中保存)

1.消息队列的特点
1 消息先进先出 FIFO 
2 消息是一个整体(用结构体来表示,结构体是自定义的结构体)
    struct msgbuf
    {
        long mtype;   //表示消息的类型 message type
        char mtext[100];  //消息的正文 message text       
    };
    
3 消息有类型,接收方可以指定接收某种类型的消息,实现对消息的过滤
2.查看和删除消息队列

ipcs -q //查看消息队列信息 ipcrm -q 23442 //删除ID是23442的消息队列

3.如何使用消息队列

(1)创建消息队列

(1)头文件及函数原型
    
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgget(key_t key, int msgflg);
(2)功能:创建消息队列
    
(3)参数:
        key_t key //和共享内存相关的key值理解相同,通过同一个key来获取同一消息队列的ID   
        int msgflg //消息队列的访问权限 0666
    
    //调用参考
    int msgid = msgget(IPC_PRIVATE, 0666);         //亲缘进程
    int msgid = msgget(44444, IPC_CREAT | 0666);   //不相关的进程
    
            
(4)返回值:
    
        成功:消息队列ID
        失败:-1       

(2)发送消息

(1)头文件及函数原型
    
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
​
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    
    struct msgbuf
    {
        long mtype;   //表示消息的类型
        char mtext[100];  //消息的正文
    };
    
(2)功能:
    发送消息
    
(3)参数:
        
        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    
        struct msgbuf
        {
            long mtype;   //表示消息的类型
            char mtext[100];  //消息的正文
        };
​
        int msqid:消息队列的ID,msgget返回值
        const void *msgp:   要发送的消息的首地址 (存放的位置)
        struct msgbuf
        {
            long mtype;   //表示消息的类型
            char mtext[100];  //消息的正文
        };
        size_t msgsz  消息正文的字节数
        int msgflg 发送方式选项 0   阻塞(如果消息队列满,再继续发送消息,会阻塞)  
                              IPC_NOWAIT 不阻塞
            
        //调用参考
        struct msgbuf a = {2,"hello"}; //消息类型是2, 消息正文是 "hello"
        msgsnd(msgid, &a, sizeof(a) - sizeof(long), 0);
        
    (4)返回值:
    
        成功 0
        失败 -1   
        
    (5)实例:
            struct msgbuf a = {2,"hello"};
            msgsnd(msgid, &a, sizeof(a) - sizeof(long), 0);     

(3)接收消息

(1)头文件及函数原型
    
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
                    
(2)功能:接收消息
    
(3)参数
        int msqid:  msgget的返回值,消息队列的ID
        void *msgp:   要接收的消息存放的位置(接收消息的缓存区) , 发送和接收结构体要对应
        
        struct mtype
        {
            long mtype;   //表示消息的类型
            char mtext[100];  //消息的正文
        };
​
        size_t msgsz:  消息正文的字节数
        long msgtyp 0   接收消息队列中第一个消息,不过滤消息
                    >0  接收消息队列中类型值为msgtyp的消息,过滤消息
            struct msgbuf b = { 0 };
            msgrcv(msgid, &b, sizeof(b)-sizeof(long),0,0); //接收消息队列中第一个消息,不过滤
            msgrcv(msgid, &b, sizeof(b)-sizeof(long),3,0);//只接收消息队列中类型为3的消息
            msgrcv(msgid, &b, sizeof(b)-sizeof(long),500,0);//只接收消息队列中类型为500的消息
​
        int msgflg  接收方式选项  0  阻塞(如果消息队列空,会阻塞等待) 
                                IPC_NOWAIT 不阻塞
        //调用参考
        struct msgbuf b = { 0 };
        msgrcv(msgid, &b, sizeof(b) - sizeof(long),15, 0);  
    
(4)返回值:
    
       >0  接收的消息的长度
       -1  出错           

(4)删除消息队列

(1)头文件及函数原型
    
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    //调用
    msgctl(msgid, IPC_RMID, NULL);
    
​
(2) 功能:     
        删除消息队列
    
(3) 参数:
    
        int msqid:  //msgget的返回值,消息队列的ID    
        int cmd:
                    IPC_STAT //读取消息队列的属性,并将其保存在buf指向的缓存区中
                    IPC_SET  //设置消息队列的属性,这个值取自buf参数
                    IPC_RMID //从系统中删除消息队列。
​
        struct msqid_ds *buf: //设置或者获取属性使用第三个参数
        //调用参考
        msgctl(msgid, IPC_RMID, NULL);
        
(4) 返回值:
    
            成功: 0
            失败: -1

16.设置线程属性

线程可以设置堆栈大小
默认情况堆栈大小(有些系统1M, 有些2M, 有些4M, 有些8M)
如果定义一个局部变量占用空间特别大,要改堆栈大小
    
pthread_create(pthread_t *thread, pthread_attr_t *attr, void*(*start_routine)(void *), void *arg);
pthread_create(&id1,NULL,fun,&age);

1. 测试线程堆栈大小

默认情况堆栈大小(有些系统1M, 有些2M, 有些4M, 有些8M) 如果定义一个局部变量占用空间特别大,要改堆栈大小

#include "my.h"
//1G == 1024M
//1M == 1024kb
//1kb == 1024字节
void *fun(void *p)
{
    char a[8*1024*1024];//8M 运行程序,段错误,栈空间不足  可以从1M开始试 2M 4M 8M
    while(1)
    {
        printf("hello world!!\n");
        sleep(1);
    }
}
int main(int argc, const char *argv[])
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    pthread_join(id, NULL);
    return 0;
}

2.修改线程堆栈大小

1.pthread_attr_init函数
需要引用的文件:

#include <pthread.h>
函数原型:

int pthread_attr_init(pthread_attr_t *attr);
参数 pthread_attr_t *attr //通过参数上地址传递的方式获取线程的属性

返回值 成功: 返回 0 失败: 返回 非零的错误编号

2.pthread_attr_setstacksize函数
需要引用头文件:

#include <pthread.h>
函数原型:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
参数 pthread_attr_t *attr //线程的属性 size_t stacksize //线程属性中的堆栈大小被设置的新的大小值 以字节为单位

返回值 成功: 返回 0 失败: 返回 非零的错误编号
3 案例代码
#include "my.h"
​
//1G == 1024M
//1M == 1024kb
//1kb == 1024字节
​
void *fun(void *p)
{
    char a[9*1024*1024];//8M 运行程序,段错误,栈空间不足
    while(1)
    {
        printf("hello world!!\n");
        sleep(1);
    }
}
​
int main(int argc, const char *argv[])
{
    pthread_t id;
​
    pthread_attr_t attr = { 0 };//定义一个结构体变量,用来保存线程的属性
    pthread_attr_init(&attr);//获取线程的默认属性
    pthread_attr_setstacksize(&attr, 10*1024*1024);//设置堆栈的大小为10M
​
    pthread_create(&id, &attr, fun, NULL);
    pthread_join(id, NULL);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值