目录
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.进程间通信方式
信号(signal)
消息队列 (message queue)
共享内存 (shared memory)
信号量 (semaphore)
套接字 (socket)//网络编程 前5种 ---在一台电脑上不同进程间能通信 第6种 ---不同主机之间不同进程间通信
1.无名管道
无名管道只能在有亲缘关系的进程中使用
无名管道是单工通信(半双工),固定读端和写端 fd[0]读端描述符 fd[1]写端描述符
无名管道不属于任何文件系统,存在内存中(内核空间)
无名管道中无数据的时候,读取数据read阻塞等待
无名管道在写入数据的时候 ,先进先出特点
无名管道在文件系统中不可见,只能通过pipe()获得到读端写端描述符,可以用read write读写。
不支持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.共享内存的特点
-
进程间通信效率最高的方式 share memory
-
使用的函数名称 (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;
}