C++ (week5):Linux系统编程2:进程

news2024/11/25 1:26:47

文章目录

    • 二、进程
      • 1.CPU的虚拟化
      • 2.进程命令
        • (1)`ps`
      • 3.进程的基本操作 (API)
        • (1)获取进程的标识 (获得进程id):getpid、getppid
        • (2)创建进程:fork()
        • (3)终止进程:exit()、_exit()、abort()、wait()、waitpid()
          • ①正常终止:exit()、_exit()
          • ②异常终止:abort()
        • (4)进程控制
          • ①孤儿进程 (Orphan Process)
          • ②僵尸进程
          • wait
          • waitpid
        • (5)执行程序:exec函数簇
      • 4.进程之间的通信 (IPC)
        • (1)管道
          • 有名管道:FIFO,named pipe
          • 无名管道(匿名管道):pipe
        • (2)共享内存
        • (3)信号量
        • (4)消息队列
      • 5.IO多路复用 (I/O multilplexing)
        • 1.select
      • 6.信号
        • (1)产生信号的4个事件源
        • (2)内核会感知事件,并给进程发送相应的信号
        • (3)信号
        • (4)信号的执行流程
        • (5)使用信号的流程
          • ①注册信号处理函数:捕获信号
          • ②发送信号
        • (6)sleep()
      • 7.其他
        • (1)crtl+D、crtl+C、crtl+Z
        • (2)printf()加不加\n的区别

二、进程

1.模型:为什么要抽象出进程?
为了进一步压榨计算机的资源。
要求:进程间隔离

2.进程是什么?
①用户角度:进程是正在执行的程序
②内核角度:进程是要执行的任务,进程是资源分配的最小单位

3.struct proc:
pid、parent、cwd、占用的内存资源、占用的外部设备资源(ofile)、CPU的状态(context)

4.如何实现进程的隔离?
时分共享。
(1)底层机制:
①指标:性能、安全、控制权
②方案一、方案二、方案三
(2)上层策略:


1.CPU的虚拟化

1.内核的职责:管理硬件资源

2.操作系统的发展:
(1)批处理系统:①队头等待 ②资源利用率低
(2)分时系统:
(3)多任务处理系统:

3.如何共享资源:时分共享(CPU)、空分共享(内存)
(1)时分共享策略:一个进程占用CPU一段时间(时间片),然后切换到另一个进程执行。但进程间的切换会有开销,进程上下文切换开销。
(2)实现:①底层机制:上下文切换 ②上层策略:调度算法(选择哪一个进程执行)

4.进程
(1)用户角度:进程就是正在执行的程序
内核角度:进程是要执行的任务。

我们不希望一个进程失败会影响另外的进程,所以进程之间必须隔离,进程之间是相互之间看不到的,感知不到其他的进程存在。
从进程的角度看,就好像它独占计算机的所有资源。
抽象机制,就是CPU的虚拟化。

5.上下文,CPU的状态依靠寄存器保存,体现了进程的动态特点
上下文切换的时机:①调用系统调用 ②切换进程

6.系统调用

7.如何实现进程的切换
(1)第一种模式:仅有内核态
效率高,但不安全

(2)第二种模式:用户态、内核态 (引入了CPU的模态机制,为了安全考虑)
用户态使用系统调用时,切换到内核态。但用户态可以不使用系统调用,导致一直不切回核心态。

(3)第三种模式:时钟中断 + 用户态、核内核态 (引入了时钟设备)
①协作式:yield(),进程让出使用权
②抢占式(非协作式):引入硬件时钟设备,时钟中断(几毫秒),执行时钟中断处理函数,切换到内核态,操作系统拿回控制权。时间片应当是时钟中断的整数倍。

①进程是资源分配的最小单位
②进程是隔离的,进程无法感知内核和其他进程


2.进程命令

(1)ps

进程快照:process snapshot
ps:显示与该终端相关的进程快照 (TTY是关联 远程控制终端)
ps x:和该用户相关的进程
ps ux
ps aux (all user)
④查进程的pid:ps aux | grep "./可执行程序名"

STAT:
S:阻塞
s:会话进程
I I I:空闲
<:高优先级
n:低优先级
+:前端
l l l:多线程

2.top
类似windows的任务管理器,每隔3秒更新信息

3.pstree
打印进程树,显示进程间的父子关系

4.前台进程 vs 后台进程
①前台进程:没有返回,但与控制终端关联
②后台进程:不会与终端控制,命令 &
jobs:查看所有的后台进程
fg 任务编号:将后台进程放到前端 foreground


3.进程的基本操作 (API)

(1)获取进程的标识 (获得进程id):getpid、getppid

①getpid:获取pid
②getppid:获取父进程的pid

只要能返回,一定执行成功

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

(2)创建进程:fork()

1.函数原型

#include <sys/types.h>	//pid_t
#include <unistd.h>		//fork()

pid_t fork(void); 

2.返回值
无参数,返回值是pid。
①创建子进程成功,父进程返回的是子进程的pid,子进程返回0;
②创建子进程失败,父进程返回-1,设置errno

父进程获得子进程的pid :fork()的返回值
子进程获得自己的pid和父进程的pid:getpid()、getppid()


3.判断是父进程还是子进程
①switch、case

pid_t pid = fork();
switch(pid){
case -1:
	error(1, errno, "fork");
case 0:
	//子进程
default:
	//父进程
}

②if、else

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if(pid == -1){
        // fork失败
		error(1 ,errno, "fork()");
    }else if(pid == 0){
        // 子进程

    }else{
        // 父进程
    }

    return 0;
}

4.fork的原理
fork()是轻量级的创建进程的函数,创建的子进程会做如下操作:
①复制父进程的proc结构体,修改pid、ppid
②复制父进程的页表,不会复制父进程的物理内存空间。不同的虚拟地址,但映射到同一个物理内存。仅在父子中某个进程进行写操作,才会发生写时复制(copy on write),才开辟新的物理内存

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

在这里插入图片描述


5.父子进程的共享、私有问题
代码段,父子进程共享
数据段、堆、栈,父子进程私有
用户态缓冲区,父子进程私有。父子进程都有自己的缓冲区
打开文件列表是父子进程共享的,共享文件的位置、偏移量,由内核管理
文件描述符列表是父子进程私有的,由进程管理

在这里插入图片描述


6.从调用fork()开始分叉,父子进程;它们不会执行fork()前面的代码,父子进程从fork()各自返回,通过返回值pid来区分父子进程。fork()后的代码会执行两次,父子进程各执行一次。

注意事项:
①子进程也是从fork返回后,开始执行
②到底是父进程先执行,还是子进程先执行,这是不确定的

在这里插入图片描述



例题1:
假定我们可以修改一个程序的源代码,我们如何在一个指定的时间获取进程的 core 文件 (当时程序执行的状态),同时让该进程可以继续执行?

#include <func.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    //执行一些代码
    //...
    
    //获取core文件,请在这里填写你的代码:
    pid_t pid = fork();
    if(pid == -1){
        error(1, errno, "fork");
    }else if(pid == 0){ //子进程
        abort();
    }else{ //父进程
        //什么都不做
    }

    //获取core文件,请在这里填写你的代码:
    pid_t pid = fork();
    switch(pid){
    case -1:  
         error(1, errno, "fork"); 
    case 0 : //子进程 
         printf("子进程\n"); 
         abort();
    default: //父进程
         printf("父进程\n");
         break; 
    } 

    //执行后续逻辑
    //...
    
    return 0;
}


(3)终止进程:exit()、_exit()、abort()、wait()、waitpid()

①正常终止:最终由系统调用_exit() 终止
②异常终止:最终由信号导致终止
在这里插入图片描述

在这里插入图片描述


①正常终止:exit()、_exit()

(1)atexit()
用函数名作为函数指针。返回0成功,非0失败。

#include <stdlib.h>
int atexit(void (*function)(void));

(2)exit()
是一个库函数,有三个步骤
①调用atexit(函数名)之前注册的函数
②刷新用户态缓冲区
③调用_exit(),正常终止进程

(3)_exit()
是一个系统调用,仅仅导致进程的终止

#include <unistd.h>
void _exit(int status);

#include <stdlib.h>
void _Exit(int status);

②异常终止:abort()

(4)异常终止:abort()
内核会给该进程发生SIGABRT信号,会导致进程异常终止。会产生coredump文件。


(5)信号导致终止:
收到信号,信号导致进程异常终止


(4)进程控制
①孤儿进程 (Orphan Process)

1.孤儿进程:父进程先于子进程死亡。子进程存活,父进程终止。

2.孤儿进程会被 init进程(PID为1的进程) 收养。
init进程会定期调用 wait() 系统调用来清理这些孤儿进程,确保它们的资源被释放。

#include <func.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    pid_t pid = fork();
    switch(pid){
    case -1:
        error(1, errno, "fork");
    case 0:
        //子进程
        sleep(2);
        printf("pid = %d, ppid = %d\n", getpid(), getppid());
        exit(0);
    default:
        //父进程
        printf("Parent: pid = %d, childPid = %d\n", getpid(), getppid());
        exit(0);
    }

    return 0;
}

在这里插入图片描述


原因是 init进程的职责就是为孤儿进程收尸

for( ;  ;){
	wait();
}

②僵尸进程

1.僵尸进程 (Zombie Process)
僵尸进程:子进程死亡,但父进程没有被回收
僵尸进程:已经终止,但其终止状态尚未被父进程获取的进程。
僵尸进程:已经终止但其父进程尚未调用 wait() 或 waitpid() 系统调用读取其退出状态的进程。

当一个进程死亡时,绝大部分信息会被释放,而有一些信息会保存在内核 (pid、退出状态、CPU时间),方便父进程以后查看这些信息。并且给父进程发给SIGCHLD信号(告诉父进程,孩子已死),但父进程默认会忽略信号。

2.父进程如何回收僵尸进程?
答:父进程手动调用wait() 和 waitpid()

3.僵尸进程不处理会造成什么影响?
子进程的proc结构体没有被回收,导致没有pid可用

僵尸进程本身不会消耗大量系统资源,但如果有大量僵尸进程未被清理,进程描述符的数量会逐渐增多,可能导致系统无法为新的进程分配进程描述符,进而影响系统性能。[大量僵尸进程会导致进程描述符耗尽]


wait

0.阻塞等待
阻塞(blocking)指的是一个进程因为某种原因(通常是等待某个事件的发生)而暂停执行,直到该事件发生后才恢复执行。阻塞等待(blocking wait)是一种常见的进程同步机制,用于确保某些操作在特定条件满足后再进行。


1.函数原型

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);  //子进程的终止状态信息

2.参数
wstatus:一个指向整数的指针,用于存储子进程的终止状态。可以通过五个宏来解析状态值。
wait的五个宏获取子进程的终止状态信息:
①WIFEXITED(wstatus):子进程是否正常终止
②WEXITSTATUS(wstatus):获取正常终止的退出状态码   _exit(status)
③WIFSIGNALED(wstatus):子进程是否异常终止
④WTERMSIG(wstatus):获取导致异常终止的信号
⑤WCOREDUMP(wstatus):是否能够产生core文件

W是wait的意思


3.返回值
①成功,返回终止的子进程的PID
②失败,返回-1,并且设置errno


4.wait()是阻塞点,会无限期阻塞,直到有子进程终止

int status;	//当作位图
pid_t childPid = wait(&status);

验证代码::


5.wait 和 waitpid 的比较
①wait:等待任意子进程终止,无法指定特定的子进程,会无限期阻塞;只能获取终止状态。
②waitpid:功能更为强大,可以指定等待特定的子进程,并且可以通过选项参数控制等待的行为。


6.代码示例

//解析子进程的退出状态
void print_wstatus(int status) {
    if (WIFEXITED(status)) {
        int exit_code = WEXITSTATUS(status);
        printf("exit_code = %d", exit_code);
    } else if (WIFSIGNALED(status)) {
        int signo = WTERMSIG(status);
        printf("term_sig = %d", signo);
#ifdef WCOREDUMP
        if (WCOREDUMP(status)) {
            printf(" (core dump)");
        }
#endif
    }
    printf("\n");
}

在这里插入图片描述

#include <func.h>

void print_wstatus(int status) {
    if (WIFEXITED(status)) {
        int exit_code = WEXITSTATUS(status);
        printf("exit_code = %d", exit_code);
    } else if (WIFSIGNALED(status)) {
        int signo = WTERMSIG(status);
        printf("term_sig = %d", signo);
#ifdef WCOREDUMP
        if (WCOREDUMP(status)) {
            printf(" (core dump)");
        }
#endif
    }
    printf("\n");
}

int main(int argc, char* argv[])
{
    pid_t pid = fork();
    switch (pid) {
    case -1:
        error(1, errno, "fork");
    case 0:
        // 子进程
        printf("CHILD: pid = %d\n", getpid());
        // sleep(2);
        // return 123;
        // exit(96);
        // _exit(9);
        // abort();
        while (1);
    default:
        // 父进程
        int status; // 保存子进程的终止状态信息, 位图。
        pid_t childPid = wait(&status); // 阻塞点:一直等待,直到有子进程终止
        if (childPid > 0) {
            printf("PARENT: %d terminated\n", childPid);
            print_wstatus(status);
        }
        exit(0);
    }
    return 0;
}

waitpid

1.函数原型

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *wstatus, int options);
wait(&wstatus) 等价于 waitpid(-1, &wstatus, 0);

2.参数
(1)pid参数
pid > 0等待指定的子进程 (进程id 等于 pid)
pid == -1等待任意子进程,此时 waitpid 的行为与 wait 相同
③pid == 0:等待同进程组的子进程 [等待任何与调用进程属于同一进程组的子进程]
④pid < -1:等待指定进程组 |pid| 的子进程 [等待进程组id 等于 |pid| (绝对值)的热议子进程]


(2)int* status 状态
用于存储子进程的终止状态。可以通过一系列宏来解析状态值,如 WIFEXITED、WEXITSTATUS、WIFSIGNALED 等

waitpid(pid, NULL, 0); //无限期阻塞等待特定子进程退出,但对其退出状态不感兴趣

(3)int options
①0:无限期阻塞等待
②WNOHANG:不阻塞。[没有任何子进程的状态发生变化,waitpid 立即返回,而不是阻塞等待]
③WUNTRACED:如果子进程进入暂停状态(如被 SIGSTOP 信号停止),waitpid 返回其状态
④WCONTINUED:如果子进程在暂停后恢复运行(如被 SIGCONT 信号继续),waitpid 返回其状态

在这里插入图片描述

3.返回值
①成功,返回状态已经改变的子进程的pid
②成功,如果设置了WNOHANG,并且没有子进程修改状态,返回0
③失败,返回-1,并设置errno

4.使用场景
①处理僵尸进程:使用waitpid()可以防止僵尸进程的出现。调用waitpid()后,子进程的资源会被释放。


(5)执行程序:exec函数簇

1.函数原型

#include <unistd.h>

extern char **environ;  //外部环境变量,字符指针数组(二级指针)

int execl(const char *path, const char *arg, ... /* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*,(char *) NULL, 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 (environment):会替换(重新设置)当前进程的环境变量 (环境变量的保存以指针数组保存)
v (vector):命令行参数以数组的形式指定,并以NULL结尾

①execl:第一个参数是路径path,根据输入的path找可执行程序,后面的参数是命令行参数(命令行及选项),以NULL结尾 (不定长)
②execlp:从系统环境变量PATH里找可执行程序,第一个参数是可执行程序名,后面的参数是命令行参数(命令行及选项),以NULL结尾 (不定长)
③v:命令行以定长数组保存,
④e:可以替换环境变量


2.环境变量environ的存储
二级指针,需要以NULL结尾
在这里插入图片描述

3.返回值
成功,不返回
失败,返回-1,并设置errno


4.exec的原理
①清除进程的代码段、数据段、堆、栈、上下文
②加载新的可执行程序,并设置代码段、数据段
不会创建新的进程,pid、parent pid不变。从新可执行程序的main函数的第一行开始执行。


5.exec的惯用法:
①先fork()
②子进程执行新的可执行程序
③父进程等待子进程结束

pid_t pid = fork();

switch(pid){
case -1:
	error(1, errno, "fork");
case 0:
	//子进程执行新的可执行程序
	execlp("sh", "sh", "-c", cmd, NULL);
	error(1, errno, "exelp");
default:
	//父进程等待子进程结束
	waitpid(pid, NULL, 0);
}

6.作业:实现一个简易的shell(命令行解释器)

for( ; ;){
	//读取用户输入的命令
	
	pid_t pid = fork();
	
	switch(pid){
	case -1:
		error(1, errno, "fork");
	case 0: 
		//子进程执行新的可执行程序
		
	default:
		//父进程等待子进程结束
		
	}
}

7.strtok()
(1)原理图

碰到分隔符,将分隔符改成’\0’,cur++,返回start

(2)demo代码



4.进程之间的通信 (IPC)

进程间通信 (interprocess communication,IPC):
①管道
②信号
③消息队列
④共享内存 + 信号量
⑤套接字

在这里插入图片描述


(1)管道

1.管道
①管道是内核管理的数据结构,管道在内核中。
②一端是写端,另一端是读端。
③管道是半双工的通信方式

在这里插入图片描述

2.阻塞点
①open阻塞:管道的读端和写端必须同时就绪,open才会返回。否则一直阻塞。
②read阻塞:当写端写入(write)数据时,读端才解除阻塞。否则会一直阻塞。

3.用两根管道实现全双工通信:点对点的聊天系统
①注意1,打开两个管道的顺序要一致,先打开同一根管道的读端和写端,否则会死锁。
②注意2,聊天一卡一卡的,输出消息时才能接收到消息。
原因:一个执行流程有多个阻塞点
解决办法:一个执行流程最多只能有一个阻塞点
③注意3,当管道的写端关闭时,读端可以读到剩余数据。如果数据都读完了,读端会读到EOF,read会返回0
④注意4,如果读端关闭,往管道写数据,内核会发送SIGPIPE信号。
【读端关闭的管道,称为broken pipe】
在这里插入图片描述


有名管道:FIFO,named pipe

1.mkfifo 管道名:创建一个有名管道



例题:使用有名管道实现远程拷贝的功能. (一个进程读文件,然后通过管道输送给另一个进程, 另一个进程写文件)。

思路:send_file.c 读源文件内容,写到管道;recv_file.c读管道,将内容写到目标文件。

//send_file.c 
#include <func.h>
#include <stdio.h>
#define MAXSIZE 1024

int main(int argc, char* argv[])
{
    if(argc != 2){
        error(1, 0 ,"Usage:%s filename",argv[0]);
    }
    
    int fd_file = open(argv[1], O_RDONLY);
    if(fd_file == -1){
        error(1, errno, "open file");
    }

    int fd_fifo = open("fifo", O_WRONLY);
    if(fd_fifo == -1){
        error(1 ,errno, "open fifo");
    }

    char buffer[MAXSIZE];
    int nbytes;
    while((nbytes = read(fd_file, buffer, MAXSIZE)) > 0){
        write(fd_fifo, buffer, nbytes);
    }
    
    close(fd_file);
    close(fd_fifo);
    
    return 0;
}
//rev_file.c 
#include <func.h>
#include <stdio.h>
#define MAXSIZE 128

int main(int argc, char* argv[])
{
    if(argc != 2){
        error(1 ,0, "Usage:%s filename",argv[1]);
    }
    
    int fd_file = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666);
    if(fd_file == -1){
        error(1, errno, "open file");
    }

    int fd_fifo = open("fifo", O_RDONLY);
    if(fd_fifo == -1){
        error(1 ,errno, "open");
    }
    
    char buffer[MAXSIZE];
    int nbytes;
    while((nbytes = read(fd_fifo, buffer, MAXSIZE)) > 0){
        write(fd_file, buffer, nbytes);
    }   
    
    close(fd_file);
    close(fd_fifo);

    return 0;
}


无名管道(匿名管道):pipe

1.概念
①无名管道,在文件系统上没有名字。(无名管道在文件流上是没有名字的。)
②无名管道只能用于有亲缘关系的进程之间通信,一般为父子关系。 [有亲缘关系就行,兄弟关系也可以]


2.系统调用:pipe()

#include <unistd.h>

int pipe(int pipefd[2]);
int pipefd[2];
if(pipe(pipefd) == -1){
	error(1, errno, "pipe");
}

3.返回值
①成功,返回0
②失败,返回-1,并设置errno

4.原理
读端关联到pipe[0]写端关联到pipe[1]
②分配fd,写给用户态空间。
在这里插入图片描述

自己给自己发消息

#include <func.h>

//自己与自己进行通信
int main(int argc, char* argv[])
{
    int pipefd[2];
    if(pipe(pipefd) == -1){
        error(1, errno, "pipe");
    }

    printf("pipefd[0] = %d, pipefd[1] = %d\n", pipefd[0], pipefd[1]);

    char buf[1024];

    write(pipefd[1], "Hello from pipe.",17); //记得为'\0'留出空间

    read(pipefd[0], buf, 1024);
    puts(buf);

    return 0;
}

在这里插入图片描述


5.父子进程通信的惯用法:
①先pipe
②后fork()    [子进程会复制父进程的文件描述符列表]
③父进程关闭管道的一端
④子进程关闭管道的另一端


(1)父子进程的半双工通信(一根管道)

#include <func.h>

int main(int argc, char* argv[])
{
    //1.先pipe()
    int pipefd[2];
    if(pipe(pipefd) == -1){
        error(1, errno, "pipe");
    }
    
    //2.后fork()
    char buffer[1024];

    switch(fork()){
    case -1:
        error(1, errno, "fork");
    case 0:
        //4.子进程关闭管道的另一端
        close(pipefd[1]);  //关闭写端
        read(pipefd[0], buffer, 1024);
        printf("Child:%s\n", buffer);
        exit(0);
    default:
        //3.父进程关闭管道的一端
        close(pipefd[0]);  //关闭读端
        /* sleep(2); */
        write(pipefd[1], "Hello from parent", 18);
        exit(0);
    }
    
    return 0;
}

在这里插入图片描述


(2)父子进程的全双工通信(两根管道)

#include <func.h>

int main(int argc, char* argv[])
{
    int pipefd1[2];
    int pipefd2[2];
    if(pipe(pipefd1) == -1){
        error(1, errno, "pipe(pipefd1)");
    }
    if(pipe(pipefd2) == -1){
        error(1, errno, "pipe(pipefd2)");
    }
    
    char buffer1[1024] = {0};
    char buffer2[1024] = {0};

    switch(fork()){
    case -1:
        error(1, errno, "fork");
    case 0:  //子进程
        close(pipefd2[0]);  //子进程关闭pipe2的读端
        write(pipefd2[1], "hello from child", 17);
        
        close(pipefd1[1]);  //子进程关闭pipe1的写端
        read(pipefd1[0], buffer1, 1024);  //子进程从pipe1读数据
        printf("Child: %s\n", buffer1);
        
        exit(0);  //子进程退出,否则会执行default部分代码
    default: //父进程
        close(pipefd2[1]);  //父进程关闭pipe2的写端
        read(pipefd2[0], buffer2, 1024);  //父进程从pipe2读数据
        printf("Parent: %s\n", buffer2);
        
        close(pipefd1[0]);  //父进程关闭pipe1的读端
        write(pipefd1[1], "Hello from parent", 18); //父进程向pipe1写数据
    }

    return 0;
}

在这里插入图片描述


(2)共享内存

(3)信号量

(4)消息队列



5.IO多路复用 (I/O multilplexing)

5种IO模型:
阻塞IO:[类似独占查询]
非阻塞式IO:配合轮询。[类似定时查询]
IO多路复用:监听多个I/O事件,将多个阻塞点变成一个阻塞点。select、poll、epoll
信号驱动IO:阻塞点主动发生信号,是异步方式。[类似中断]
异步IO:不需要CPU主动处理 [类似DMA方式]

在这里插入图片描述


1.select

1.函数原型

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
select(6, &readfds, &writefds, NULL, &timeout)

①nfds:监听的最大文件描述符+1 [为了内核提升效率,只检查前几nfds个文件描述符]
②readfds:传入(调用时),表示对哪些文件描述符的读事件感兴趣 ;传出(函数返回时),读事件已就绪的文件描述符
③writefds:传入(调用时),表示对哪些文件描述符的写事件感兴趣 ;传出(函数返回时),写事件已就绪的文件描述符
④exceptfds:传入(调用时),表示对哪些文件描述符的异常事件感兴趣 ;传出(函数返回时),发生异常事件的文件描述符
⑤timieout:超时时间,最多阻塞的时间长度。[超过就不要了]。
i.定时等待:{秒,微秒}
ii.无限等待:若为NULL,则无限期阻塞
iii.立即返回:若为{0,0},则不阻塞,立刻返回

timieout也是传入传出参数,传入时是超时时间,传出时是剩余时间。返回值为0,则表示超时(时间用完,但没有时间就绪)。

fds:文件描述符集合 (file descriptor set)

select是同步的,当select返回时,说明有事件就绪了。【中途会切换到其他进程,到下次select检查时,若有一个或多个事件就绪,就返回。若超时,返回0。若未超时,继续切换其他进程等待(事件就绪)】
多个阻塞点,变成了select一个阻塞点


2.数据类型
(1)fd_set:传入传出参数(指针),大小为1024的位图
①FD_ZERO(&set)
②FD_SET(fd, &set)
③FD_ISSET(fd, &set)
④FD_CLR(fd, &set)

(2)struct timeval:{tv_sec, tv_usec}


3.select的返回时机
①有事件就绪
②超时时间到达
③被信号中断
在这里插入图片描述

4.返回值
①成功,返回就绪事件的个数。
②成功,如果超时,返回0
③失败,返回-1,并设置errno


5.原理
select 是一种系统调用,用于在多个文件描述符上进行多路复用,以便监视多个文件描述符的可读、可写或异常状态。


6.select的缺陷
监听数量有限:监听的文件描述符的个数是有限的。fd_set是大小为1024的位图,最大只能监听1024个fd。
效率低:返回值只能表示就绪的事件数量,但不知道具体是哪个事件就绪。需要遍历fd_set,找到就绪的文件描述符。时间复杂度为O(n)。(若场景为10万个事件在连接,但只有10个事件就绪,也需要遍历10万个事件)


7.select 的应用场景
select 主要用于网络服务器和客户端,允许在单线程中高效处理多个连接或文件描述符的 I/O 操作。

select 主要用于需要同时监视多个文件描述符的场景,如网络服务器需要同时处理多个客户端连接。通过使用 select,程序可以在一个线程中处理多个连接,而不需要为每个连接创建一个线程,从而减少资源开销。


8.select介绍
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


8.select_pipe 实现点对点聊天

mkfifo pipe1
mkfifo pipe2
//select_p1.c
#include <func.h>
#include <stdio.h>

#define MAXLINE 256

int main(int argc, char* argv[])
{
    int fd1 = open("pipe1", O_WRONLY);
    if(fd1 == -1){
        error(1, errno, "open pipe1");
    }

    int fd2 = open("pipe2", O_RDONLY);
    if(fd2 == -1){
        error(1, errno, "open pipe2");
    }

    printf("Estalibshed.\n");

    char recvline[MAXLINE];
    char sendline[MAXLINE];

    fd_set mainfds; //局部变量,定义一个文件描述符集合
    FD_ZERO(&mainfds);  //清空,将所有的位 置为0
    FD_SET(STDIN_FILENO, &mainfds);
    int maxfds = STDIN_FILENO;

    FD_SET(fd2, &mainfds);
    if(fd2 > maxfds){
        maxfds = fd2;
    }
    
    for( ; ; ){
        fd_set readfds = mainfds;  //结构体复制

        int events = select(maxfds + 1, &readfds, NULL, NULL, NULL);
        switch(events){
        case -1:
            error(1, errno, "select");
        case 0:
            //超时
            printf("TIMEOUT\n");
            continue;
        default:    //返回就绪事件的个数
            //STDIN_FILENO 就绪
            if(FD_ISSET(STDIN_FILENO, &readfds)){
                //一定不会阻塞
                fgets(sendline, MAXLINE, stdin);
                write(fd1, sendline, strlen(sendline) + 1);  // +1: '\0'
            }
            //pipe2就绪
            if(FD_ISSET(fd2, &readfds)){
                //一定不会阻塞
                int nbytes = read(fd2, recvline, MAXLINE);
                switch(nbytes){
                case 0:
                    //管道的写端关闭了
                    goto end;
                case -1:
                    error(1, errno, "read pipe2");
                default:
                    printf("from p2: %s", recvline);
                }
            }
        }
    }   
end:
    close(fd1);
    close(fd2);

    return 0;
}
//select_p2.c
#include <func.h>
#include <stdio.h>

#define MAXLINE 256

int main(int argc, char* argv[])
{
    int fd1 = open("pipe1", O_RDONLY);
    if(fd1 == -1){
        error(1, errno, "open pipe1");
    }

    int fd2 = open("pipe2", O_WRONLY);
    if(fd2 == -1){
        error(1, errno, "open pipe2");
    }

    printf("Estalibshed.\n");

    char recvline[MAXLINE];
    char sendline[MAXLINE];

    fd_set mainfds; //局部变量,定义一个文件描述符集合
    FD_ZERO(&mainfds);  //清空,将所有的位 置为0
    FD_SET(STDIN_FILENO, &mainfds);
    int maxfds = STDIN_FILENO;

    FD_SET(fd1, &mainfds);
    if(fd1 > maxfds){
        maxfds = fd1;
    }
    
    for( ; ; ){
        fd_set readfds = mainfds;  //结构体复制

        int events = select(maxfds + 1, &readfds, NULL, NULL, NULL);
        switch(events){
        case -1:
            error(1, errno, "select");
        case 0:
            //超时
            printf("TIMEOUT\n");
            continue;
        default:    //返回就绪事件的个数
            //STDIN_FILENO 就绪
            if(FD_ISSET(STDIN_FILENO, &readfds)){
                //一定不会阻塞
                fgets(sendline, MAXLINE, stdin);
                write(fd2, sendline, strlen(sendline) + 1);  // +1: '\0'
            }
            //pipe2就绪
            if(FD_ISSET(fd1, &readfds)){
                //一定不会阻塞
                int nbytes = read(fd1, recvline, MAXLINE);
                switch(nbytes){
                case 0:
                    //管道的写端关闭了
                    goto end;
                case -1:
                    error(1, errno, "read pipe1");
                default:
                    printf("from p1: %s", recvline);
                }
            }
        }
    }   
end:
    close(fd1);
    close(fd2);

    return 0;
}



6.信号

1.信号异步的事件通知机制。 [例如select是一种IO事件通知机制]
信号是应用程序感知外界的桥梁。
原理:事件源发生了事件,内核发送信号给应用程序。

在这里插入图片描述


2.信号的特点:
①不稳定
②异步的 (什么时候收到信号是不确定的,收到信号后会立刻马上执行信号处理函数)
③信号的语义,在不同系统中不一样


(1)产生信号的4个事件源

1.硬件:
①访问非法的内存:SIGSEGV (段错误,segment volation)
②执行非法的指令:SIGILL (illegal)
③算数异常 (除0):SIGFPE (浮点异常,float point exception)

2.内核:
①写一个读端关闭的管道 (broken pipe):SIGPIPE

3.应用程序:
①自己调用abort():SIGABRT
②子进程终止:SIGCHLD

4.用户:
①crtl +C:SIGINT,终止进程
②crtl + \:SIGQUIT,终止进程,并生成核心转储文件 (core dump)
③crtl + Z:SIGTSTP,暂停进程,将进程挂起到后台
④kill命令:kill -SIGINT 子进程pid


(2)内核会感知事件,并给进程发送相应的信号

①事件源产生事件
②内核感知事件的发生,产生信号,先pending (未决信号),在下次调度进程时,将信号发生给进程
③进程收到信号,会立刻处理

在这里插入图片描述


(3)信号

1.man 7 signal
(1)默认处理方式:signal dispositons (不捕获信号的情况)
在这里插入图片描述

(2)标准信号:standard signal
SIGKILL:不能被捕获,杀死进程
SIGSTOP:不能被捕获,暂停进程

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


2.kill -l
在这里插入图片描述
SIGSEGV:段错误 (segment volation)
①尝试访问未分配的内存地址。
②尝试写入只读内存区域。
③访问超过数组边界的内存。
④解引用空指针或无效指针。


(4)信号的执行流程

signal:信号处理函数,捕获信号

信号是异步的,什么时候收到信号是不确定的。

在这里插入图片描述

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

signal(SIGINT, SIG_IGN);

第二个参数:handler是信号处理函数,也可以用两个宏
SIG_IGN:忽略
SIG_DFL:默认

在这里插入图片描述


(5)使用信号的流程
①注册信号处理函数:捕获信号

步骤:①注册信号 ②编写handler函数

但有两个信号不可被捕获:SIGKILL、SIGSTOP

#include <func.h>

void handler(int signo){
    switch(signo){
    case SIGINT:
        printf("Caught SIGINT\n");
        break;
    case SIGTSTP:
        printf("Caught SIGTSTP\n");
        break;
    case SIGQUIT:
        printf("Caught SIGQUIT\n");
        break;
    default:
        printf("Unknown %d\n",signo);
    }
}

int main(int argc, char* argv[])
{
    //注册信号处理函数(捕获信号)
    sighandler_t oldhandler = signal(SIGINT, handler);
    
    if(oldhandler == SIG_ERR){
        error(1, errno, "signal %d", SIGINT);
    }

    oldhandler = signal(SIGTSTP, handler);
    if(oldhandler == SIG_ERR){
        error(1, errno, "signal %d", SIGTSTP);
    }

    oldhandler = signal(SIGQUIT, handler);
    if(oldhandler == SIG_ERR){
        error(1, errno, "signal %d",SIGQUIT);
    }
    
    for( ; ; ){
        sleep(1);
    }

    return 0;
}

②发送信号

1.kill命令
kill -SIGKILL pid, ...kill -信号编号 pid1 pid2 ...


2.系统调用:kill
(1)函数原型

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

pid:
①>0:给指定的进程发送信号
②=0:给同进程组的所有进程发送信号
③-1:给所有能够发送信号的进程发送信号 (除了init)
④<-1:给指定进程组的进程发送信号

(2)返回值
①成功,返回0 (有一个就算成功)
②失败,返回-1,并设置errno


3.库函数:raise()
(1)函数原型
在这里插入图片描述

(2)返回值:
成功,返回0
失败,返回非0

(3)作用:
raise函数用于向当前进程发送信号。换句话说,它是给自己(当前进程)发送信号。通过raise函数,程序可以引发一个信号,从而调用预先定义的信号处理程序。这在模拟某些异常或中断处理场景时非常有用。


(6)sleep()

执行态到阻塞态,睡若干秒
头文件:#include <unistd.h>



7.其他

(1)crtl+D、crtl+C、crtl+Z

①ctrl + D:EOF,文件结束/输入结束
②ctrl + C:终止进程。[用户按下 ctrl+c ,将导致内核向进程发送一个 SIGINT 的信号]
③ctrl + Z:暂停,挂起进程,放入后台。挂起的进程可以通过命令 fg(将进程恢复到前台运行)或 bg(在后台继续运行)来管理。[SIGTSTP]
④crtl + \:退出进程 [SIGQUIT]


(2)printf()加不加\n的区别

用户态缓冲区:分别给stdin、stdout、stderr流分了一部分
刷新用户态缓冲区,是将用户态缓冲区的内容写回内核态缓冲区,再写入dev1文件。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 源码学习记录:散列 (dict)

散列 Redis 源码版本&#xff1a;Redis-6.0.9&#xff0c;本篇文章的代码均在 dict.h / dict.c 文件中。 散列类型可以存储一组无需的键值对&#xff0c;他特别适用于存储一个对象数据。 字典 Redis 通常使用字典结构体存储用户散列数据。字典是 Redis 的重要数据结构。除了散…

AGM AG32 (MCU + FPGA)在音频处理上的应用案例

AGM AG32 (MCU FPGA)在音频处理&#xff0c;比如语音识别、降噪、声音增强等方面上的应用很多&#xff0c;结合AG32的特点&#xff0c;这里做一个简单的分享。 AGM AG32的架构是FPGA MCU&#xff0c;并且AG32与STM32的关键兼容&#xff0c;这个低成本的AG32器件&#xff0c;在…

MongoDB基础入门到深入(八)MongoDB整合SpringBoot、Chang Streams

文章目录 系列文章索引十五、MongoDB整合SpringBoot1、环境准备2、集合操作3、文档操作&#xff08;1&#xff09;相关注解&#xff08;2&#xff09;创建实体&#xff08;3&#xff09;添加文档&#xff08;4&#xff09;查询文档&#xff08;5&#xff09;更新文档&#xff0…

paligemma、Grounding-DINO-1.5简单无需标注无需训练直接可以使用的VLM图像到文本模型

1、paligemma 参考:https://github.com/google-research/big_vision/blob/main/big_vision/configs/proj/paligemma/README.md 模型架构: 文本与图像特征一起送入大模型 在线体验网址: https://huggingface.co/spaces/big-vision/paligemma 通过文字prompt既可与图片对话…

工行音视频服务平台建设与应用经验

近些年来&#xff0c;伴随着技术能力的积累突破&#xff0c;音视频服务开始蓬勃生长走进千家万户&#xff0c;使用远程视频通话、观看各类视频直播逐渐成为人们的日常&#xff0c;而金融服务作为社会生活的重要组成部分&#xff0c;自然需要积极拥抱应用新技术。 如今&#xff…

目标检测——无人机垃圾数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

【C++入门】—— C++入门 (下)_内联函数

前言&#xff1a;在了解完前面的C基础内容后&#xff0c;马上我们就要真正不如C的学习了&#xff0c;但在之前让我们最后了解最后一点点C入门知识&#xff01;来迟的520特别篇&#xff01; 本篇主要内容&#xff1a; 内联函数 auto关键字 范围for 指针空值nullptr C入门 1. 内联…

Science| 单体耦合纤维实现无芯片纺织电子(纤维器件/智能织物/柔性可穿戴电子)

东华大学Hongzhi Wang,Chengyi Hou和Qinghong Zhang团队在《Science》上发布了一篇题为“Single body-coupled fiber enables chipless textile electronics”的论文。论文内容如下: 一、 摘要 智能纺织品为将技术融入日常生活中提供了理想的平台。然而,目前的纺织电子系统…

飞凌嵌入式亮相上海CPSE,展现智能充储技术新力量

5月22日~24日&#xff0c;第三届上海国际充电桩及换电站展览会(CPSE)在上海汽车会展中心举行&#xff0c;飞凌嵌入式以“聚焦充电桩主控智造赋能车桩智联”为主题参展&#xff0c;与来自全国的客户朋友及行业伙伴一同交流分享&#xff0c;展位号Z15。 作为国内较早从事嵌入式技…

HTTPS:安全网络通信的基石

在数字化时代&#xff0c;网络通信的安全变得至关重要。HTTPS&#xff08;超文本传输安全协议&#xff09;是一种用于保护网络通信的协议&#xff0c;它通过加密技术确保数据传输的安全性和完整性。下面我们就来了解一下HTTPS。 一、HTTPS是什么&#xff1f; HTTPS是HTTP&…

罗德与施瓦茨FPS7频谱分析仪怎么判断真实信号?

频谱分析仪是电子测量领域的重要仪器&#xff0c;可以帮助工程师、研究人员分析信号的频域特性&#xff0c;为设备调试、故障诊断等提供有价值的数据支持。作为业界领先的频谱分析仪制造商&#xff0c;罗德与施瓦茨的FPS7型号在精度、灵敏度和分辨率等指标上都有出色表现&#…

《最新出炉》系列初窥篇-Python+Playwright自动化测试-39-highlight() 方法之追踪定位

1.简介 在之前的文章中宏哥讲解和分享了&#xff0c;为了看清自动化测试的步骤&#xff0c;通过JavaScript添加高亮颜色&#xff0c;就可以清楚的看到执行步骤了。在学习和实践Playwright的过程中&#xff0c;偶然发现了使用Playwright中的highlight()方法也突出显示Web元素。…

macOS Monterey 12.7.5 (21H1222) 正式版发布,ISO、IPSW、PKG 下载

macOS Monterey 12.7.5 (21H1222) 正式版发布&#xff0c;ISO、IPSW、PKG 下载 5 月 13 日凌晨&#xff0c;macOS Sonoma 14.5 发布&#xff0c;同时带来了 macOS Ventru 13.6.7 和 macOS Monterey 12.7.5 安全更新。 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Appli…

【qt】QListWidget 组件

QListWidget 组件 一.QListWidget的用途二.界面设计三.QListWidget的添加1.界面添加2.代码添加 四.列表项的设置1.文本2.图标3.复选框4.列表大小 五.字体和图标的设置1.字体&#xff1a;2.图标&#xff1a; 六.设置显示模式1.图标2.列表 七.其他功能实现1.删除2.全选3.反选4.ad…

服务高峰期gc,导致服务不可用

随着应用程序的复杂性和负载的不断增加&#xff0c;对JVM进行调优&#xff0c;也是保障系统稳定性的一个重要方向。 需要注意&#xff0c;调优并非首选方案&#xff0c;一般来说解决性能问题还是要从应用程序本身入手&#xff08;业务日志&#xff0c;慢请求等&#xff09;&am…

今日刷三题(day13):变态跳台阶+包含不超过两种字符的最长字串+字符串的排列

题目一&#xff1a;变态跳台阶 题目描述&#xff1a; 一只青蛙一次可以跳上1级台阶&#xff0c;也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。 输入输出描述&#xff1a; 输入&#xff1a;3 输出&#xff1a;…

SAPUI5基础知识2 - 手动创建一个SAPUI5的项目

1. 前言 在本篇文章中&#xff0c;我们将手动一步一步建立出第一个SAPUI5的 ‘Hello World!’ 项目。 2. 步骤详解 2.1 在BAS中建立Dev Space 进入SAP Business Application Studio的Dev Space Manger&#xff0c;选择创建Dev Space。 勾选HTML5 Application Template插件…

C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

由于删除、修改、重装QT库引起的软件问题@FreeBSD

由于由于删除、修改、重装QT库以及snappy库等&#xff0c;导致很多软件出现了异常&#xff0c;即无法启动&#xff0c;逐个解决问题。 qutebrowser浏览器 报错&#xff1a; qutebrowser报错 No backend library found qutebrowser needs QtWebKit or QtWebEngine, but neith…

Ollama本地运行 Mistral-7B-Instruct-v0.3

Ollama本地运行 Mistral-7B-Instruct-v0.3 0. 引言1. 运行 mistral:7b-instruct-v0.3-q8_02. 简单问个问题 0. 引言 Mixtral 5月23日发布了 Mistral-7B-Instruct-v0.3&#xff0c;支持 function calling&#xff0c;今天简单运行一下。 1. 运行 mistral:7b-instruct-v0.3-q8_…