(二)Web服务器之Linux多进程

一、基础概念

Linux操作系统一般由以下四个主要部分组成:内核、shell、文件系统和应用程序 。
Linux系统组成图

  • 内核(Kernel):是操作系统的核心部分,负责管理系统的硬件资源、进程管理、内存管理、文件系统等。它直接与硬件交互,为其他所有程序提供接口。
  • Shell(命令行解释器):是一个程序,用于接收用户输入的命令并执行相应的操作。它提供了一个用户界面,让用户可以与操作系统进行交互。
  • 文件系统(File System):是一种组织和管理文件的方法,它将数据存储在磁盘上,并允许用户对这些数据进行访问和操作。
  • 应用程序(Applications):是用户使用的软件,如文本编辑器、浏览器、游戏等。

开发应用程序需要开发者编写程序, 程序是包含一系列信息的文件,这些信息描述了如何创建一个进程。进程是程序的实例,可以由一个程序来创建多个进程。

  • 在传统操作系统中,进程即是系统资源分配的基本分配单元,也是系统动态执行的基本执行单元。
  • 从内核的角度看,进程虚拟地址空间由用户内存空间(程序代码及其使用的变量)和一系列内核数据结构(维护进程状态信息包括进程控制块(PCB)、进程队列、进程调度器等 )组成。
    在这里插入图片描述

操作系统以时间片(timeslice,操作系统分配给每个正在运行的进程微观上的一段CPU时间,在Linux上大概5ms-800ms)为单位,由内核计算分配给各个用户/进程服务,各个用户/进程可通过终端与计算机进行交互,优先响应一些紧急任务,某些紧急任务不需时间片排队。
在这里插入图片描述

  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

进程转换状态是进程在其生命周期中可能经历的不同状态。当一个进程从一个状态转换到另一个状态时,它可能需要等待某些条件满足或执行某些操作才能完成状态的转换。
在这里插入图片描述

  • 新建态:当一个进程被创建时,它处于新建态。此时,它的所有资源尚未分配给它,并且它还没有运行。

  • 运行态:一旦进程获得了所需的资源并开始执行,它就进入了运行态。在运行态下,进程正在使用处理器执行代码,并且可能会不断地切换到其他就绪态来等待更合适的时机运行。

  • 就绪态:就绪态是指进程已经具备运行条件,等待系统分配处理器以便运行。当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。

  • 阻塞态:阻塞态是指进程因为某些原因无法继续执行而进入的一种等待状态。常见的阻塞原因包括等待输入/输出操作完成、等待信号量或锁的释放等。在阻塞态下,进程不会占用CPU资源,而是等待相应条件的满足。

  • 终止态:终止态是指进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然占用其所分配的资源,直到操作系统将其回收。

为了管理进程,每个进程会分配一个PCB(Processing ControlBlock)进程控制块,维护进程相关的信息。

# struct task_struct 为Linux内核的进程控制块,以下路径可查看其定义(不同版本位置可能有变)
/usr/src/kernels/3.10.0-1160.80.1.el7.x86 64/include/linux/sched.h  # 查看进程可以使用的资源上限
ulimit -a:# 查看进程信息
ps aux / ajxa:显示终端上的所有进程,包括其他用户的进程u:显示进程的详细信息x:显示没有控制终端的进程j:列出与作业控制相关的信息# 实时显示进程动态
top# 列出所有信号
kill -l 
# 停止指定pid的进程
kill -9 pid
# 停止指定name的进程
kill -9 name 

每个进程都由进程号来标识,其类型为pid_t(整型),进程号的范围:0~32767。进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。

任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。

进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当前的进程组号。
进程号和进程组相关函数:
pid t getpid(void);
pid t getppid(void) ;
pid_t getpgid(pid_t pid);

二、进程创建

/*#include <sys/types.h>#include <unistd.h>pid_t fork(void);函数的作用:用于创建子进程。返回值:fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。在父进程中返回创建的子进程的ID,在子进程中返回0如何区分父进程和子进程:通过fork的返回值。在父进程中返回-1,表示创建子进程失败,并且设置errno父子进程之间的关系:区别:1.fork()函数的返回值不同父进程中: >0 返回的子进程的ID子进程中: =02.pcb中的一些数据当前的进程的id pid当前的进程的父进程的id ppid信号集共同点:某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作- 用户区的数据- 文件描述符表父子进程对变量是不是共享的?- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。*/#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main() {int num = 10;// 创建子进程pid_t pid = fork();// 判断是父进程还是子进程if(pid > 0) {// printf("pid : %d\n", pid);// 如果大于0,返回的是创建的子进程的进程号,当前是父进程printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());printf("parent num : %d\n", num);num += 10;printf("parent num += 10 : %d\n", num);} else if(pid == 0) {// 当前是子进程printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());printf("child num : %d\n", num);num += 100;printf("child num += 100 : %d\n", num);}// for循环for(int i = 0; i < 3; i++) {printf("i : %d , pid : %d\n", i , getpid());sleep(1);}return 0;
}/*
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
*/

exec函数族(一系列功能相似的函数,类似函数重载的函数)
根据指定文件路径找到可执行文件,并用其取代调用进程的内容。
执行成功不会返回,因为其调用进程除了进程ID等表面信息保持原样,其余如代码段、数据栈和堆栈等都被新内容取代,失败返回-1,原程序调用点继续向下执行。

一般fork后,在子进程中使用该函数

/*  #include <unistd.h>int execl(const char *path, const char *arg, ...);- 参数:- path:需要指定的执行的文件的路径或者名称a.out /home/nowcoder/a.out 推荐使用绝对路径./a.out hello world- arg:是执行可执行文件所需要的参数列表第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称从第二个参数开始往后,就是程序执行所需要的的参数列表。参数最后需要以NULL结束(哨兵)- 返回值:只有当调用失败,才会有返回值,返回-1,并且设置errno如果调用成功,没有返回值。*/
#include <unistd.h>
#include <stdio.h>int main() {// 创建一个子进程,在子进程中执行exec函数族中的函数pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n",getpid());sleep(1);}else if(pid == 0) {// 子进程// execl("hello","hello",NULL);execl("/bin/ps", "ps", "aux", NULL);perror("execl");printf("i am child process, pid : %d\n", getpid());}for(int i = 0; i < 3; i++) {printf("i = %d, pid = %d\n", i, getpid());}return 0;
}

三、进程退出

孤儿进程指一个父进程退出后,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作 。

僵尸进程指一个子进程在其父进程还没有调用wait()或waitpid()的情况下退出。这个子进程就是僵尸进程。当父进程结束时,它的子进程成为了“孤儿进程”,因为它们没有其他进程等待它们结束。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作 。

https://blog.csdn.net/u014530704/article/details/73848573
用fork创建新进程后,常在新进程中调用exec族函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。

进程退出、孤儿进程、僵尸进程
常用c语言库的exit函数退出
孤儿进程就是父进程创建的子进程,但是父进程挂了,子进程没挂,被init领养着;
僵尸进程就是子进程执行完了,但是父进程还在运行,但是没wait它,所以没法被kill杀死的进程

四、进程间通信(重点)

在这里插入图片描述

在这里插入图片描述

匿名管道

在这里插入图片描述
管道的特点
在这里插入图片描述
在这里插入图片描述

为什么使用管道可以进行进程间通信?
因为子进程和父进程在内核中的文件描述符表中的文件描述符指向相同的文件

管道的数据结构:
在这里插入图片描述

在这里插入图片描述

/*实现 ps aux | grep xxx 父子进程间通信子进程: ps aux, 子进程结束后,将数据发送给父进程父进程:获取到数据,过滤pipe()execlp()子进程将标准输出 stdout_fileno 重定向到管道的写端。  dup2
*/#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>int main() {// 创建一个管道int fd[2];int ret = pipe(fd);if(ret == -1) {perror("pipe");exit(0);}// 创建子进程pid_t pid = fork();if(pid > 0) {// 父进程// 关闭写端close(fd[1]);// 从管道中读取char buf[1024] = {0};/*设置管道非阻塞int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flagflags |= O_NONBLOCK;            // 修改flag的值fcntl(fd[0], F_SETFL, flags);   // 设置新的flagwhile(1) {int len = read(pipefd[0], buf, sizeof(buf));printf("len : %d\n", len);printf("parent recv : %s, pid : %d\n", buf, getpid());memset(buf, 0, 1024);sleep(1);}
*/int len = -1;while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {// 过滤数据输出printf("%s", buf);memset(buf, 0, 1024);}wait(NULL);} else if(pid == 0) {// 子进程// 关闭读端close(fd[0]);// 文件描述符的重定向 stdout_fileno -> fd[1]dup2(fd[1], STDOUT_FILENO);// 执行 ps auxexeclp("ps", "ps", "aux", NULL);perror("execlp");exit(0);} else {perror("fork");exit(0);}return 0;
}

管道的读写特点:
使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程
也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。

4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程
也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,
直到管道中有空位置才能再次写入数据并返回。

总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待

写管道:管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)管道读端没有全部关闭:管道已满,write阻塞管道没有满,write将数据写入,并返回实际写入的字节数

用于有父子关系或者兄弟关系等亲缘关系的进程间的通信
pipe函数

有名管道

在这里插入图片描述
在这里插入图片描述

/*创建fifo文件1.通过命令: mkfifo 名字2.通过函数:int mkfifo(const char *pathname, mode_t mode);#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);参数:- pathname: 管道名称的路径- mode: 文件的权限 和 open 的 mode 是一样的是一个八进制的数返回值:成功返回0,失败返回-1,并设置错误号*/#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>int main() {// 判断文件是否存在int ret = access("fifo1", F_OK);if(ret == -1) {printf("管道不存在,创建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {perror("mkfifo");exit(0);}       }return 0;
}

内存映射

在这里插入图片描述

/*#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);- 功能:将一个文件或者设备的数据映射到内存中- 参数:- void *addr: NULL, 由内核指定- length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。获取文件的长度:stat lseek- prot : 对申请的内存映射区的操作权限-PROT_EXEC :可执行的权限-PROT_READ :读权限-PROT_WRITE :写权限-PROT_NONE :没有权限要操作映射内存,必须要有读的权限。PROT_READ、PROT_READ|PROT_WRITE- flags :- MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项- MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)- fd: 需要映射的那个文件的文件描述符- 通过open得到,open的是一个磁盘文件- 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。prot: PROT_READ                open:只读/读写 prot: PROT_READ | PROT_WRITE   open:读写- offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不便宜。- 返回值:返回创建的内存的首地址失败返回MAP_FAILED,(void *) -1int munmap(void *addr, size_t length);- 功能:释放内存映射- 参数:- addr : 要释放的内存的首地址- length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
*//*使用内存映射实现进程间通信:1.有关系的进程(父子进程)- 还没有子进程的时候- 通过唯一的父进程,先创建内存映射区- 有了内存映射区以后,创建子进程- 父子进程共享创建的内存映射区2.没有关系的进程间通信- 准备一个大小不是0的磁盘文件- 进程1 通过磁盘文件创建内存映射区- 得到一个操作这块内存的指针- 进程2 通过磁盘文件创建内存映射区- 得到一个操作这块内存的指针- 使用内存映射区通信注意:内存映射区通信,是非阻塞。
*/#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>// 作业:使用内存映射实现没有关系的进程间的通信。
int main() {// 1.打开一个文件int fd = open("test.txt", O_RDWR);int size = lseek(fd, 0, SEEK_END);  // 获取文件的大小// 2.创建内存映射区void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(ptr == MAP_FAILED) {perror("mmap");exit(0);}// 3.创建子进程pid_t pid = fork();if(pid > 0) {wait(NULL);// 父进程char buf[64];strcpy(buf, (char *)ptr);printf("read data : %s\n", buf);}else if(pid == 0){// 子进程strcpy((char *)ptr, "nihao a, son!!!");}// 关闭内存映射区munmap(ptr, size);return 0;
}

内存映射的注意事项:

1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(…);
ptr++; 可以对其进行++操作
munmap(ptr, len); // 错误,要保存地址

2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误,返回MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致。

3.如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍,返回MAP_FAILED

4.mmap什么情况下会调用失败?
- 第二个参数:length = 0
- 第三个参数:prot
- 只指定了写权限
- prot PROT_READ | PROT_WRITE
第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY

5.可以open的时候O_CREAT一个新文件来创建映射区吗?
- 可以的,但是创建的文件的大小如果为0的话,肯定不行
- 可以对新的文件进行扩展
- lseek()
- truncate()

6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open(“XXX”);
mmap(,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。

7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,);
4K
越界操作操作的是非法的内存 -> 段错误

信号

信号是事件发生时对进程的通信机制,也被称为软件中断,是一种软件层次对中断机制的一种模拟,是一种异步通信的方式。

比如,

  • 对于前台进程,比如xshell中命令终端,ctrl+c就会让内核给进程发送一个中断信号,让当前进程中止
  • 系统状态变化,比如alarm定时器到期引起的发送SIGALRM信号
  • kill命令,比如调用kill函数
  • 硬件发生异常

在这里插入图片描述

kill -l  # 查看系统定义的信号列表
1-31 	 # 标准信号(常用的9号)
32、33   # 没有
34-64    # 实时信号(目前没有使用未来可能使用的预定义信号)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内核产生信号、信号未被处理即未决、信号被进程接受并处理即递达

在这里插入图片描述
三个数字中间的数字就行,最常见的arm的x86架构的值,其他架构规定的信号不用管;

默认处理动作总Core文件是啥?演示一下,先一个错误程序的代码如下:

#include<stdio.h>
#include<string.h>int main(){char * buf;strcpy(buf, "hello");return  0;
}

程序访问非法内存,看core文件查看错误信息

ulimit -a # 查看一些系统配置信息
ulimit -c 1024 # 更改生成core文件的大小
gcc core.c -g # 生成可调试文件
./a.out # 运行可执行文件,产生错误文件core
gdb a.out #打开调试
core-file core #打开错误文件

信号相关函数

/*  #include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);- 功能:给任何的进程或者进程组pid, 发送任何的信号 sig- 参数:- pid :> 0 : 将信号发送给指定的进程= 0 : 将信号发送给当前的进程组= -1 : 将信号发送给每一个有权限接收这个信号的进程< -1 : 这个pid=某个进程组的ID取反 (-12345)- sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号kill(getppid(), 9);kill(getpid(), 9);int raise(int sig);- 功能:给当前进程发送信号- 参数:- sig : 要发送的信号- 返回值:- 成功 0- 失败 非0kill(getpid(), sig);   void abort(void);- 功能: 发送SIGABRT信号给当前的进程,杀死当前进程kill(getpid(), SIGABRT);
*/#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = fork();if(pid == 0) {// 子进程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父进程printf("parent process\n");sleep(2);printf("kill child process now\n");kill(pid, SIGINT);}return 0;
}

定时器函数alarm

/*#include <unistd.h>unsigned int alarm(unsigned int seconds);- 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM- 参数:seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。取消一个定时器,通过alarm(0)。- 返回值:- 之前没有定时器,返回0- 之前有定时器,返回之前的定时器剩余的时间- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。alarm(10);  -> 返回0过了1秒alarm(5);   -> 返回9alarm(100) -> 该函数是不阻塞的
*/#include <stdio.h>
#include <unistd.h>int main() {int seconds = alarm(5);printf("seconds = %d\n", seconds);  // 0sleep(2);seconds = alarm(2);    // 不阻塞printf("seconds = %d\n", seconds);  // 3while(1) {}return 0;
}

alarm函数在调用该函数的进程上设置一个定时时间,时间到了就发送信号终止该进程,该进程二次调用该函数的话会返回上次定时器的剩余时间并重新开始定时,这意味着我想提前终止这个进程的话alarm(0);

SIG_SKILL 和SIG_SOP不能被捕捉是因为kill是杀死一个进程的,如果能被捕捉,那么一个进程不能被杀死那就很可怕了;

/*#include <sys/time.h>int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);- 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时- 参数:- which : 定时器以什么时间计时ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRMITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF- new_value: 设置定时器的属性struct itimerval {      // 定时器的结构体struct timeval it_interval;  // 每个阶段的时间,间隔时间struct timeval it_value;     // 延迟多长时间执行定时器};struct timeval {        // 时间的结构体time_t      tv_sec;     //  秒数     suseconds_t tv_usec;    //  微秒    };过10秒后,每个2秒定时一次- old_value :记录上一次的定时的时间参数,一般不使用,指定NULL- 返回值:成功 0失败 -1 并设置错误号
*/#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>// 过3秒以后,每隔2秒钟定时一次
int main() {struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}
/*#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);- 功能:设置某个信号的捕捉行为- 参数:- signum: 要捕捉的信号- handler: 捕捉到信号要如何处理- SIG_IGN : 忽略信号- SIG_DFL : 使用信号默认的行为- 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。回调函数:- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义- 不是程序员调用,而是当信号产生,由内核调用- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。- 返回值:成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL失败,返回SIG_ERR,设置错误号SIGKILL SIGSTOP不能被捕捉,不能被忽略。
*/#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void myalarm(int num) {printf("捕捉到了信号的编号是:%d\n", num);printf("xxxxxxx\n");
}// 过3秒以后,每隔2秒钟定时一次
int main() {// 注册信号捕捉// signal(SIGALRM, SIG_IGN);// signal(SIGALRM, SIG_DFL);// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。signal(SIGALRM, myalarm);struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if(ret == -1) {perror("setitimer");exit(0);}getchar();return 0;
}

信号集
在这里插入图片描述
PCB进程控制块,有文件描述符表,pid等
在这里插入图片描述

内核实现信号捕捉函数的流程

在这里插入图片描述

SIGCHLD信号

通过这个信号可以解决多进程中的僵尸进程问题;

子进程结束时,父进程有责任回收它的资源,而wait是阻塞的,父进程也有自己的事情做,这个时候就可以用这个信号

在这里插入图片描述

共享内存

共享内存比内存映射效率更高,是效率最高的ICP技术!

在这里插入图片描述
在这里插入图片描述

共享内存相关的函数
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);- 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。新创建的内存段中的数据都会被初始化为0- 参数:- key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。一般使用16进制表示,非0- size: 共享内存的大小- shmflg: 属性- 访问权限- 附加属性:创建/判断共享内存是不是存在- 创建:IPC_CREAT- 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用IPC_CREAT | IPC_EXCL | 0664- 返回值:失败:-1 并设置错误号成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。void *shmat(int shmid, const void *shmaddr, int shmflg);- 功能:和当前的进程进行关联- 参数:- shmid : 共享内存的标识(ID),由shmget返回值获取- shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定- shmflg : 对共享内存的操作- 读 : SHM_RDONLY, 必须要有读权限- 读写: 0- 返回值:成功:返回共享内存的首(起始)地址。  失败(void *) -1int shmdt(const void *shmaddr);- 功能:解除当前进程和共享内存的关联- 参数:shmaddr:共享内存的首地址- 返回值:成功 0, 失败 -1int shmctl(int shmid, int cmd, struct shmid_ds *buf);- 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。- 参数:- shmid: 共享内存的ID- cmd : 要做的操作- IPC_STAT : 获取共享内存的当前的状态- IPC_SET : 设置共享内存的状态- IPC_RMID: 标记共享内存被销毁- buf:需要设置或者获取的共享内存的属性信息- IPC_STAT : buf存储数据- IPC_SET : buf中需要初始化数据,设置到内核中- IPC_RMID : 没有用,NULLkey_t ftok(const char *pathname, int proj_id);- 功能:根据指定的路径名,和int值,生成一个共享内存的key- 参数:- pathname:指定一个存在的路径/home/nowcoder/Linux/a.txt/ - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节范围 : 0-255  一般指定一个字符 'a'问题1:操作系统如何知道一块共享内存被多少个进程关联?- 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch- shm_nattach 记录了关联的进程个数问题2:可不可以对共享内存进行多次删除 shmctl- 可以的- 因为shmctl 标记删除共享内存,不是直接删除- 什么时候真正删除呢?当和共享内存关联的进程数为0的时候,就真正被删除- 当共享内存的key为0的时候,表示共享内存被标记删除了如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。共享内存和内存映射的区别1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)2.共享内存效果更高3.内存所有的进程操作的是同一块共享内存。内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。4.数据安全- 进程突然退出共享内存还存在内存映射区消失- 运行进程的电脑死机,宕机了数据存在在共享内存中,没有了内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。5.生命周期- 内存映射区:进程退出,内存映射区销毁- 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机如果一个进程退出,会自动和共享内存进行取消关联。

守护进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。

消息队列

Linux进程间通信的消息队列是通过系统调用来实现的。具体来说,有两个常用的系统调用与消息队列相关:

  1. msgget():该函数用于创建或获取一个消息队列。如果消息队列不存在,则创建一个新的;如果已经存在,则返回该消息队列的标识符。

  2. msgsnd():该函数用于向消息队列中发送一条消息。它需要指定消息队列的标识符、要发送的消息以及要发送的消息长度。如果消息队列中没有可用的空间,则会阻塞进程直到有空间可用为止。

  3. msgrcv():该函数用于从消息队列中接收一条消息。它需要指定消息队列的标识符以及要接收的消息长度。如果消息队列中没有可用的消息,则会阻塞进程直到有消息可用为止。

除了上述三个系统调用之外,还有其他一些与消息队列相关的系统调用,例如msgctl()msgctl(SIOC_RMID)等。这些系统调用的具体用法可以参考相关的手册页(man page)。

信号量、和Socket在后文网络通信和多线程中穿插

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/149772.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机组成 课程笔记】7.2 DRAM和SRAM

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 7 - 2 - 702-DRAM和SRAM&#xff08;13-22--&#xff09;_哔哩哔哩_bilibili 从【计算机组成 课程笔记】7.1 存储层次结构概况_Elaine_Bao的博客-CSDN博客中&#xff0c;我们了解到&#xff1a;SRAM比较快&#x…

mysql的mvcc详解

一 MVCC的作用 1.1 mvcc的作用 1.MVCC&#xff08;Multiversion Concurrency Control&#xff09;多版本并发控制。即通过数据行的多个版本管理来实现数据库的并发控制&#xff0c;使得在InnoDB事务隔离级别下执行一致性读操作有了保障。 2.mysql中的InnoDB中实现了MVCC主要…

Go Gin Gorm Casbin权限管理实现 - 3. 实现Gin鉴权中间件

文章目录 0. 背景1. 准备工作2. gin中间件2.1 中间件代码2.2 中间件使用2.3 测试中间件使用结果 3. 添加权限管理API3.1 获取所有用户3.2 获取所有角色组3.3 获取所有角色组的策略3.4 修改角色组策略3.5 删除角色组策略3.6 添加用户到组3.7 从组中删除用户3.8 测试API 4. 最终目…

Leetcode1071. 字符串的最大公因子(三种方法,带详细解析)

&#x1f3b6;Leetcode1071. 字符串的最大公因子 对于字符串 s 和 t&#xff0c;只有在 s t … t&#xff08;t 自身连接 1 次或多次&#xff09;时&#xff0c;我们才认定 “t 能除尽 s”。 给定两个字符串 str1 和 str2 。返回 最长字符串 x&#xff0c;要求满足 x 能除尽…

c#设计模式-行为型模式 之 状态模式

&#x1f680;简介 状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态改变时改变其行为&#xff0c;我们可以通过创建一个状态接口和一些实现了该接口的状态类来实现状态模式。然后&#xff0c;我们可以创建一个上下文类&#xff0c;它会根据其当前的状态对象来改…

学习笔记|ADC反推电源电压|扫描按键(长按循环触发)|课设级实战练习|STC32G单片机视频开发教程(冲哥)|第十八集:ADC实战

文章目录 1.ADC反推电源电压测出Vref引脚电压的意义?手册示例代码分析复写手册代码Tips&#xff1a;乘除法与移位关系为什么4096后面还有L 2.ADC扫描按键(长按循环触发)长按触发的实现 3.实战小练1.初始状态显示 00 - 00 - 00&#xff0c;分别作为时&#xff0c;分&#xff0c…

黑豹程序员-架构师学习路线图-百科:JSON替代XML

文章目录 1、数据交换之王2、XML的起源3、JSON诞生4、什么是JSON 1、数据交换之王 最早多个软件之间使用txt进行信息交互&#xff0c;缺点&#xff1a;纯文本&#xff0c;无法了解其结构&#xff1b;之后使用信令&#xff0c;如&#xff1a;电话的信令&#xff08;拨号、接听、…

【程序员必看】计算机网络,快速了解网络层次、常用协议和物理设备!

文章目录 0 引言1 基础知识的定义1.1 计算机网络层次1.2 网络供应商 ISP1.3 猫、路由器、交换机1.4 IP协议1.5 TCP、UDP协议1.6 HTTP、HTTPS、FTP协议1.7 Web、Web浏览器、Web服务器1.8 以太网和WLAN1.9 Socket &#xff08;网络套接字&#xff09; 2 总结 0 引言 在学习的过程…

常用Redis界面化软件

对于Redis的操作&#xff0c;前期有过介绍【Centos 下安装 Redis 及命令行操作】。而在Redis的日常开发调试中&#xff0c;可使用可视化软件方便进行操作。 本篇主要介绍Redis可视化的两款工具&#xff1a;Redis Desktop Manager和AnotherRedisDesktopManager。 1、Redis Desk…

Aasee Api开放平台上线啦!

使用方法 首先介绍使用方法&#xff0c;只需导入一个SDK即可使用实现调用第三方的接口&#xff0c;那如何导入SDK呢&#xff0c;目前jar已经上传至maven中心仓库可直接引入到pom文件中使用&#xff0c;下面是例子&#xff1a; <dependency><groupId>io.github.Aa…

【Java 进阶篇】使用 JDBCTemplate 执行 DML 语句详解

JDBCTemplate 是 Spring 框架中的一个核心模块&#xff0c;用于简化 JDBC 编程&#xff0c;使数据库操作更加便捷和高效。在本文中&#xff0c;我们将重点介绍如何使用 JDBCTemplate 执行 DML&#xff08;Data Manipulation Language&#xff09;语句&#xff0c;包括插入、更新…

SNP Glue:SAP数据导入到其他系统的多种方式

SAP是一款功能强大的企业资源计划&#xff08;ERP&#xff09;软件&#xff0c;许多企业依赖SAP来管理和处理其核心业务数据。然而&#xff0c;有时候企业需要将SAP中的数据导入到其他系统中&#xff0c;以实现更广泛的数据共享和集成&#xff0c;便于企业实现数据智能。本文将…

计算摄像技术02 - 颜色空间

一些计算摄像技术知识内容的整理&#xff1a;颜色视觉与感知特性、颜色空间和基于彩色滤镜阵列的彩色感知。 文章目录 一、颜色视觉与感知特性 &#xff08;1&#xff09;色调 &#xff08;2&#xff09;饱和度 &#xff08;3&#xff09;明度 二、颜色空间 &#xff08;1&…

[架构之路-228]:目标系统 - 纵向分层 - 计算机硬件与体系结构 - 硬盘存储结构原理:如何表征0和1,即如何存储0和1,如何读数据,如何写数据(修改数据)

目录 前言&#xff1a; 一、磁盘的盘面组成 1.1 磁盘是什么 ​编辑1.2 磁盘存储介质 1.3 磁盘数据的组织 1.3.1 分层组织&#xff1a;盘面号 1.3.2 扇区和磁道 1.3.3 数据 1.3.4 磁盘数据0和1的存储方式 1.3.5 磁盘数据0和1的修正方法 1.3.6 磁盘数据0和1的读 二、…

基于腾讯云的OTA远程升级

一、OTA OTA即over the air,是一种远程固件升级技术&#xff0c;它允许在设备已经部署在现场运行时通过网络远程更新其固件或软件。OTA技术有许多优点&#xff0c;比如我们手机系统有个地方做了优化&#xff0c;使用OTA技术我们就不用召回每部手机&#xff0c;直接通过云端就可…

(一)正点原子STM32MP135移植——准备

一、简述 使用板卡&#xff1a;正点原子的ATK-DLMP135 V1.2 从i.mx6ull学习完过来&#xff0c;想继续学习一下移植uboot和内核的&#xff0c;但是原子官方没有MP135的移植教程&#xff0c;STM32MP157的移植教程用的又是老版本的代码&#xff0c;ST官方更新后的代码不兼容老版本…

Linux中的wc命令

2023年10月6月&#xff0c;周五晚上 目录 wc命令的主要功能和用法如下:统计文件行数、字数和字节数只统计行数只统计字数只统计字节数 wc命令在Linux/Unix系统中是word count的缩写,它用来统计文件的行数、字数和字节数。 wc命令的主要功能和用法如下: 统计文件行数、字数和字…

mac清理垃圾的软件有哪些?这三款我最推荐

没错&#xff0c;Mac电脑真的好用&#xff0c;但是清理系统垃圾可不是件容易的事。由于Mac系统的封闭性&#xff0c;系统的缓存垃圾常常隐藏得让人发现不了。不过&#xff0c;别担心&#xff01;有一些专业的Mac清理软件可以帮你解决这一系列问题&#xff0c;让清理垃圾变得轻松…

踩大坑ssh免密登录详细讲解

目 录 问题背景 环境说明 免密登录流程说明 1.首先要在对应的用户主机名的情况下生成密钥对&#xff0c;在A服务器执行 2.将A服务器d公钥拷贝到B服务器对应的位置 3.在A服务器访问B服务器 免密登录流程 0.用户说明 1.目前现状演示 2.删除B服务器.ssh 文件夹下面的…

【将文本编码为图像灰度级别】以 ASCII 编码并与灰度级别位混合将文本字符串隐藏到图像像素的最低位中,使其不明显研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…