进程间通信(匿名管道 创建管道及分配任务代码)

news2024/11/22 9:00:51

文章目录

  • 一.进程间通信
    • 进程为什么要通信?
    • 进程如何通信
  • 二.管道
    • 匿名管道
    • pipe
      • 写端慢写入,读端等待
      • 写端写入,读端不读 && 管道的大小
      • 写端关闭,读端不会读取
      • 写端写入,读端关闭
      • 字节流
    • 总结
    • 安全问题
  • 三.进程池
    • 创建管道 && 分配任务的代码:

一.进程间通信

进程为什么要通信?

进程需要某种协同–>通过通信的方式进行协同。(学校教导处查看某个教室某时间有位置,跟老师协商这个时间是否可行)
进程 = 内核数据结构 + 代码和数据。
数据有类别:通知进程就绪(通知学生上课)、单纯的要传递数据(老师把期末分数交给教务系统)、控制相关的信息(拖堂太久,主任强制老师下课)
进程具有独立性。进程蹦了只是删自己的内核数据结构和代码与数据,跟其它进程没关。

进程如何通信

进程间通信,成本可能稍微高一些!
进程间通信的前提:让不同的进程,看到同一份(操作系统的)资源(“一段内存”)。
在这里插入图片描述
因为A进程开辟的空间B进程是看不到的(独立性),只有两个进程看到同一块内存才能通信。
所以得让第三方(操作系统)提供一块内存。
在这里插入图片描述

二.管道

匿名管道

打开两次文件,需要创建两次struct file。第二次创建struct file时,因为属性是一样的,所以不用再次加载文件属性和内核级文件缓冲区。操作系统不喜欢做浪费时间和浪费空间的事情。
在这里插入图片描述
创建子系统时,task_struct和struct files_struct要保证进程的独立性,所以要拷贝。
但文件系统没有必要保证独立性,所以直接用类似于浅拷贝的指针。
在这里插入图片描述
子进程继承父进程的描述符表,就会指向同一个文件,也就意味着都会写进同一个内核里。当操作系统刷新时,都会指向同一个文件。
这就是为什么父子进程打印数据时,都会向一个显示器终端打印。
问题:进程默认会打开三个标准输入输出:0,1,2。怎么做到的?
答:bash打开了,那么bash的子进程也就默认打开了。
问题:为什么子进程主动关闭close(0,1,2),不影响父进程继续使用显示器文件?
答:sturct file中有引用计数(ref_count;)。
类似这么一个代码:file ->ref_count--; if(ref_count==0) //释放文件资源
综上所述:父子进程看到了同一个文件,其中的内存级的缓冲区就相当于管道文件
管道只允许单向通信,因为简单。
在这里插入图片描述
要么父进程一直给子进程发消息,要么子进程一直给父进程发消息。
父子进程关闭不需要的文件描述符。父进程关闭r,子进程关闭w。
在这里插入图片描述
并且内核级缓冲区不再写入磁盘,只让父子进程进行内存级的通信。
创建管道的具体过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
问题:父子既然要关闭不需要的fd,为什么曾经要打开呢?
答:如果父进程只创建读方式,子进程只能继承读方式。一个管道不能存在两个读或者两个写,通信不了。如果以读写的方式把文件打开,万一父进程在写,子进程也在写,就出问题了。
在这里插入图片描述
可以不关吗,父进程只用fd== 3 ,子进程只用fd==4?
可以不关闭,并不影响通信,但建议关了,万一误写了呢!
一个进程中存储的文件描述符是有上限的,所以一个进程能打开的文件是有限的。占着一个不用,就浪费了资源,可能造成文件描述符泄漏。

pipe

管道的创建,单独设计了pipe的系统调用,底层就是open。
在这里插入图片描述
返回值:若成功返回0,失败返回-1
在这里插入图片描述

创建的通道不再向磁盘中刷新数据
在这里插入图片描述
pipe若不需要文件路径和文件名,创建出来的叫做匿名管道。
问题:如果我想双向通信呢?
答:创建两个通道
问题:管道为什么要单向通信?
答:复用文件系统的代码,为了简单,减少开发成本。父子进程都往一个管道里读写,就要区分哪些是父进程的读写,哪些是子进程的读写。
文件描述符0,1,2是系统默认的,所以管道的文件描述符是3和4:

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>

int main()
{
    // 1.创建管道
    int pipefd[2];
    int n = pipe(pipefd); //输出型参数,rfd,wfd
    if(n != 0) //失败
    {
        std::cerr << "errno" << errno << ":" << "errstring" << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    return 0;
}

其中pipefd[0]->0是读端;pipefd[1]->write是写端
在这里插入图片描述

写端慢写入,读端等待

父进程读取,子进程写入:(因为管道也是文件,所以读写管道的接口是read/write)

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

const int size = 1024; //读取数组的大小

// 计数 + pid
std::string getOtherMessage()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt);
    cnt++;
    pid_t self_id = getpid();
    std::string string_pid = std::to_string(self_id);
    std::string message = "messageid: ";
    message += messageid;
    message += " my pid is : ";
    message += string_pid;

    return message;
}

// 子进程进行写入
void SubProcessWrite(int wfd)
{
    std::string message = "father,I am your son process!";
    while(true)
    {
        std::string info = message + getOtherMessage(); // 子进程发给父进程的消息
        write(wfd,info.c_str(),info.size()); //写入管道的时候没有写入"\0",没有必要写入

        sleep(1); //让子进程写慢一点
    }
}

// 父进程进行读取
void FatherProcessWrite(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        //ssize_t就是int
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer)-1); //-1为了给"\0"留位置
        if(n > 0)
        {
            inbuffer[n] = 0 ; // == "\0"
            std::cout << "father get message: " << inbuffer << std::endl;
        }
    }
}

int main()
{
    // 1.创建管道
    int pipefd[2];
    int n = pipe(pipefd); //输出型参数,rfd,wfd
    if(n != 0) //失败
    {
        std::cerr << "errno" << errno << ":" << "errstring" << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    sleep(1); // 创建完管道等待1秒

    // 2.创建子进程
    pid_t id =  fork();
    if(id == 0)
    {
        std::cout << "子进程关闭不需要的fd了,准备发通信" << std::endl;
        sleep(1);
        // 子进程 --> write
        // 3.关闭不需要的文件描述符
        close(pipefd[0]);
        SubProcessWrite(pipefd[1]);
        // 用完之后关闭
        close(pipefd[1]);
        exit(0);
    }
    std::cout << "父进程关闭不需要的fd了,准备收通信" << std::endl;
    sleep(1);
    // 父进程 --> read
    // 3.关闭不需要的文件描述符
    close(pipefd[1]);
    FatherProcessWrite(pipefd[0]);
    // 用完之后关闭
    close(pipefd[0]);

    //防止子进程僵尸
    pid_t rid = waitpid(id,nullptr,0);
    if(rid > 0)
    {
        std::cout << "wait child process done" << std::endl;
    }
    return 0;
}

现象:
在这里插入图片描述
根据上述代码和现象,阐述一些结论:
在子进程中有sleep(1),父进程没有sleep()。子进程有写的有多慢,父进程负责打印的就有多慢。
在这里插入图片描述
子进程写一条,父进程读一条,父进程等待子进程的写入。

写端写入,读端不读 && 管道的大小

如果让子进程疯狂写,父进程不读,会发生什么?并且管道的大小是多少?

// 子进程进行写入
void SubProcessWrite(int wfd)
{
    int pipesize = 0;  //计数
    std::string message = "father,I am your son process!";
    while(true)
    {
        char c = 'A';  //每次只写一个字节
        write(wfd,&c,1);
        std::cout << "pipesize: " << ++pipesize << std::endl;
    }
}

// 父进程进行读取
void FatherProcessWrite(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        sleep(500);
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer)-1); //-1为了给"\0"留位置
        if(n > 0)
        {
            inbuffer[n] = 0 ; // == "\0"
            std::cout << "father get message: " << inbuffer << std::endl;
        }
    }
}

发现一共写了65536字节(64KB)就停了
在这里插入图片描述
ubuntu 20…04的版本管道大小是64KB,不同的操作系统管道的大小是不一样的,跟系统有关。

写端关闭,读端不会读取

当子进程写完退出并关闭管道,父进程还会读取吗?

// 子进程进行写入
void SubProcessWrite(int wfd)
{
    int pipesize = 0;
    std::string message = "father,I am your son process!";
    while(true)
    {
        char c = 'A';
        write(wfd,&c,1);
        std::cout << "pipesize: " << ++pipesize << std::endl;
        break;
    }
    std::cout << "child quit" << std::endl;
}

// 父进程进行读取
void FatherProcessWrite(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer)-1); //-1为了给"\0"留位置
        if(n > 0)
        {
            inbuffer[n] = 0 ; // == "\0"
            std::cout << "father get message: " << inbuffer << std::endl;
        }
        std::cout << "father get return val: " << n << std::endl;
    }
}

发现read的返回值是0,表示写端已经关闭了,管道已经失效了。
在这里插入图片描述

写端写入,读端关闭

rfd直接关闭,写端wfd一直在进行写入,会发生什么?
在这里插入图片描述
验证:

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

const int size = 1024; //读取数组的大小

// 计数 + pid
std::string getOtherMessage()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt);
    cnt++;
    pid_t self_id = getpid();
    std::string string_pid = std::to_string(self_id);
    std::string message = "messageid: ";
    message += messageid;
    message += " my pid is : ";
    message += string_pid;

    return message;
}

// 子进程进行写入
void SubProcessWrite(int wfd)
{
            char c = 'A';
    int pipesize = 0;
    std::string message = "father,I am your son process!";
    while(true)
    {
        std::string info = message + getOtherMessage(); // 子进程发给父进程的消息
        write(wfd,info.c_str(),info.size()); //写入管道的时候没有写入"\0",没有必要写入
        sleep(1); //让子进程写慢一点
    }
    std::cout << "child quit" << std::endl;
}

// 父进程进行读取
void FatherProcessWrite(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        //ssize就是int
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer)-1); //-1为了给"\0"留位置
        if(n > 0)
        {
            inbuffer[n] = 0 ; // == "\0"
            std::cout << "father get message: " << inbuffer << std::endl;
        }
        else if(n == 0) //写端关闭,读到了文件的结尾
        {
            std::cout << "client quit,father get return val: " << n << "father quit too!" << std::endl;
            break;
        }
        else if(n < 0) //读取失败
        {
            std::cerr << "read error" << std::endl;
            break;
        }
        sleep(1);
        break;
    }
}

int main()
{
    // 1.创建管道
    int pipefd[2];
    int n = pipe(pipefd); //输出型参数,rfd,wfd
    if(n != 0) //失败
    {
        std::cerr << "errno" << errno << ":" << "errstring" << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    sleep(1); // 创建完管道等待1秒

    // 2.创建子进程
    pid_t id =  fork();
    if(id == 0)
    {
        std::cout << "子进程关闭不需要的fd了,准备发通信" << std::endl;
        sleep(1);
        // 子进程 --> write
        // 3.关闭不需要的文件描述符
        close(pipefd[0]);
        SubProcessWrite(pipefd[1]);
        // 用完之后关闭
        close(pipefd[1]);
        exit(0);
    }
    std::cout << "父进程关闭不需要的fd了,准备收通信" << std::endl;
    sleep(1);
    // 父进程 --> read
    // 3.关闭不需要的文件描述符
    close(pipefd[1]);
    FatherProcessWrite(pipefd[0]);
    std::cout << "5s,father close rfd" << std::endl;
    sleep(5);
    // 用完之后关闭
    close(pipefd[0]);

    int status = 0;
    //得到子进程的退出信息
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;
        std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0x7f) << std::endl;
    }
    return 0;
}

13号信号
在这里插入图片描述
管道文件再通信的时候,是面向字节流的。

字节流

例子:子进程不断写入,父进程隔2秒在读取。并且子进程写入时往cerr里写入,父进程时往cout中打印

// 子进程进行写入
void SubProcessWrite(int wfd)
{
    char c = 'A';
    int pipesize = 0;
    std::string message = "father,I am your son process!";
    while(true)
    {
        std::cerr << "+++++++++++++++++++++++" << std::endl;
        std::string info = message + getOtherMessage(); // 子进程发给父进程的消息
        write(wfd,info.c_str(),info.size()); //写入管道的时候没有写入"\0",没有必要写入
        std::cerr << info << std::endl;
    }
    std::cout << "child quit" << std::endl;
}

// 父进程进行读取
void FatherProcessWrite(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        sleep(2);
        std::cout << "--------------------------" << std::endl;
        //ssize就是int
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer)-1); //-1为了给"\0"留位置
        if(n > 0)
        {
            inbuffer[n] = 0 ; // == "\0"
            std::cout << "father get message: " << inbuffer << std::endl;
        }
        else if(n == 0) //写端关闭,读到了文件的结尾
        {
            std::cout << "client quit,father get return val: " << n << "father quit too!" << std::endl;
            break;
        }
        else if(n < 0) //读取失败
        {
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

运行时,让cerr与cout的打印到不同的显示屏上,看出效果

./testpiper 2 > /dev/pts/1

看出write的次数和读取的次数不是一一匹配的,这就是面向字节流最典型的特点。
在这里插入图片描述
非字节流的例子:用邮箱发文件,发4份文件,每份文件是独立的,点完一封看下一封。

总结

管道的4种状态:
1.如果管道内部是空的 && write的wfd(写端)没有关闭,读取条件不具备,读进程会被阻塞,等待读取的条件具备。
2.如果管道被写满 && read的rfd(读端)不读且没有关闭,写进程会被阻塞(管道被写满–>写条件不具备),等待写条件具备。
3.管道一直在读 && write的wfd(写端)关闭了,读端read返回值==0,表示读到了文件结尾。
4.read的rfd(读端)直接关闭 && write的wfd(写端)一直在进行写入。写端进程会被操作系统直接使用13号信号关掉,相当于进程出现了异常。

管道的5种特征:
1.匿名管道:只用来进行具有"血缘关系的"进程之间的通信,可以用于兄弟、爷孙进程,常用于父子进程。(无法让两个毫不相关的文件看到同一个文件)
2.管道内部,自带进程之间的同步机制。(明显的顺序性,一读一写)
3.文件的生命周期随进程。(在系统层面上发现没有与该文件关联的进程,就会把该文件释放掉)->管道也是文件,道理一样。
4.管道文件在通信的时候,是面向字节流。
5.管道的通信模式,是一种特殊的半双工模式。
全双工:跟别人发生口角,你问候他,他问候你,你在输入的时候还在输出。
半双工:现实说话,你一句我一句,但不要同时说。

安全问题

在这里插入图片描述
常见的"|" 命令就是匿名管道,有多少"|"就有多少管道,先创建管道后创建子进程。且子进程的父进程都是bash,说明子进程之间有“血缘关系”。
在这里插入图片描述

三.进程池

平常写的Shell都是读到命令了才创建子进程。
进程池是先提前创建子进程,然后等待父进程分发任务,通过管道相连。
在这里插入图片描述

创建管道 && 分配任务的代码:

子进程继承父进程的代码,所以提前把任务的代码写好,并且组成一张代码表,这样管道只用传4字节的数组下标即可,相当于任务码。
在这里插入图片描述
代码如下:
ProcessPool.cc

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"

// master
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string &name)
        : _wfd(wfd), _subprocessid(id), _name(name)
    {}
    int GetWfd() { return _wfd; }
    pid_t GetProcessId() { return _subprocessid; }
    std::string GetName() { return _name; }
    void CloseChannel()
    {
        close(_wfd);
    }
    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait " << rid << " success" << std::endl;
        }
    }
    ~Channel()
    {}

private:
    int _wfd;            // 读端fd
    pid_t _subprocessid; // 子进程的pid
    std::string _name;   // 管道的名字
};

// C++形参命名规范
// const &:输入型参数
//& :输出输入型参数
//* :输出型参数
// 创建信号和子进程
void CreatChannelAndSub(int num, std::vector<Channel> *channels, task_t task) // task_t task->回调函数
{
    for (int i = 0; i < num; i++)
    {
        // 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if (n < 0)
            exit(1);
        // 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            if(!channels->empty()) //第二次开始,清除管道
            {
                for(auto& channel : *channels)
                {
                    channel.CloseChannel();
                }
            }
            // child - read
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 重定向到0,好处:读取的任务都去0读
            task();
            exit(0);
        }
        // 构建管道名字
        std::string channels_name = "Channel-" + std::to_string(i);
        // father - write
        close(pipefd[0]);
        channels->push_back(Channel(pipefd[1], id, channels_name));
    }
}

// 轮询方案,防止任务不均衡
int NextChannel(int channelnum) 
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}

// 发出信号
void SendTaskCommand(Channel &channel, int taskcommand)
{
    write(channel.GetWfd(), &taskcommand, sizeof(taskcommand)); 
}

//运行一次任务
void CtrlProcessOnce(std::vector<Channel> &channels)
{
    sleep(1);
    // a.选择一个任务
    int taskcommand = SelectTask();
    // b.选择一个信道和进程
    int channel_index = NextChannel(channels.size());
    // 发送任务
    SendTaskCommand(channels[channel_index], taskcommand);
    std::cout << std::endl;
    std::cout << "taskcommand: " << taskcommand << " channel: "
              << channels[channel_index].GetName() << " sub process: "
              << channels[channel_index].GetProcessId() << std::endl;
}

// 通过Channel控制子进程
void CtrlProcess(std::vector<Channel> &channels, int times = -1) // times为运行的次数
{
    if (times > 0)
    {
        while (times--)
        {
            CtrlProcessOnce(channels);
        }
    }
    else
    {
        while (true)
        {
            CtrlProcessOnce(channels);
        }
    }
}

// 回收管道和子进程
void CleanUpChannel(std::vector<Channel> &channels)
{
    for (auto &channels : channels)
    {
        // a.关闭所有写端
        channels.CloseChannel();
        // b.回收子进程
        channels.Wait();
    }
}

// ./processPool 5 创建5个管道
int main(int argc, char *argv[])
{
    if (argc != 2) // 输入错误
    {
        std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;
        return 1;
    }
    int num = std::stoi(argv[1]); // char*->int
    LoadTask();                   // 加载任务
    std::vector<Channel> channels;
    // 1.创建信号和子进程
    CreatChannelAndSub(num, &channels, work);
    // 2.通过Channel控制子进程
    CtrlProcess(channels, 10);
    // 3.回收管道和子进程
    CleanUpChannel(channels);

    return 0;
}

.hpp是定义和声明在一起,缺点是不能打包成库,大多用于开源项目。
Task.hpp

#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>

#define TaskNum 3 //任务的数量

typedef void (*task_t)();

task_t tasks[TaskNum];

void Print()
{
    std::cout << "Print task" << std::endl;
}

void Download()
{
    std::cout << "Download task" << std::endl;
}

void Flush()
{
    std::cout << "Flush task" << std::endl;
}

void LoadTask()  //下载任务
{
    srand(time(nullptr) ^ getpid());  //随机数
    tasks[0]= Print;
    tasks[1]= Download;
    tasks[2]= Flush;
}

void ExcuteTask(int number)  //执行任务
{
    if(number < 0 || number > 2) return ;
    tasks[number]();
}

int SelectTask()
{
    return rand() % TaskNum;
}

void work()
{
    while(true)
    {
        int command=0;
        int n = read(0,&command,sizeof(command));
        if(n==sizeof(int))
        {
            std::cout << "pid is:" << getpid() << "handler task" << std::endl;
            ExcuteTask(command); //执行任务
        }
        else if(n == 0) //执行完毕
        {
            std::cout << "sub process: " << getpid() << " quit" << std::endl;
            break;
        }
    }
}

效果:
执行任务
在这里插入图片描述
退出任务,并且回关闭通道与回收子进程
在这里插入图片描述
有个细节需要注意:
这是创建第一个管道:
在这里插入图片描述
问题就出现在第二次往后的管道创建,因为子进程继承父进程的文件描述符表,所以第二个子进程的fd==4是指向0号通道的写端,这样造成两个写端和一个读端,所以得把多余的写端删掉,否则释放管道的时候,导致管道中的计数器ref_count !=0 造成无法释放。子进程也无法退出的情况。
在这里插入图片描述

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

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

相关文章

VADv2 论文学习

VADv2: End-to-End Vectorized Autonomous Driving via Probabilistic Planning 解决了什么问题&#xff1f;相关工作感知运动预测规划自动驾驶领域的大语言模型 提出了什么方法&#xff1f;场景编码器概率规划训练分布损失冲突损失场景 Token 损失 推理 实验实验设定指标消融实…

AI类课程的笔记

信息论、导论、模式识别&#xff08;数据挖掘&#xff09;、语义网络与知识图谱、深度学习、强化学习 &#xff08;零&#xff09;信息论 详见另一篇博文 信息论自总结笔记(仍然在更新)_信息论也更新了-CSDN博客https://blog.csdn.net/sinat_27382047/article/details/12690…

【Unity踩坑】Unity导出的UWP项目编译失败

在Unity中导出了UWP平台的项目后&#xff08;Xaml或D3D&#xff09;&#xff0c;使用Visual Studio编译时发生错误&#xff1a; Error: Unity.IL2CPP.Building.BuilderFailedException: Lump_libil2cpp_vm.cpp 查找后发现是Visual Studio 与Unity兼容的问题 原贴&#xff1a;…

数据分析案例-机器学习工程师薪资数据可视化分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

bus中设备驱动的probe触发逻辑和device、driver的添加逻辑

注&#xff1a;以下的代码皆摘自于linux 4.9.88版本的内核源码&#xff0c;不同版本可能有所出入。 往期内容&#xff1a; 驱动中的device和device_driver结构体bus总线的相关结构体和注册逻辑 1. driver的probe触发方式 在 Linux 设备模型中&#xff0c;probe() 函数是驱动…

自动驾驶系列—智能驾驶中的“换挡革命”:线控换挡技术详解

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

Java垃圾回收简述

什么是Java的垃圾回收&#xff1f; 自动管理内存的机制&#xff0c;负责自动释放不再被程序引用的对象所占用的内存。 怎么触发垃圾回收&#xff1f; 内存不足时&#xff1a;JVM检测到堆内存不足时&#xff0c;无法为新的对象分配内存时&#xff0c;会自动触发垃圾回收。手动…

UDP协议【网络】

文章目录 UDP协议格式 UDP协议格式 16位源端口号&#xff1a;表示数据从哪里来。16位目的端口号&#xff1a;表示数据要到哪里去。16位UDP长度&#xff1a;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的长度。16位UDP检验和&#xff1a;如果UDP报文的检验和出错&…

【电路】1.2 电流和电压的参考方向

1.2 电流和电压的参考方向 参考方向就是一个假设的方向&#xff08;类似中学物理的正方形&#xff09;&#xff0c;选参考方向的原因是电路结构略显复杂&#xff0c; 无法直接看出支路电压、电流的实际方向&#xff0c;参考方向可以任意选择&#xff0c;任意支路电压、电流只有…

【韩顺平Java笔记】第8章:面向对象编程(中级部分)【272-284】

272. 包基本介绍 272.1 看一个应用场景 272.2 包的三大作用 272.3 包的基本语法 273. 包原理 274. 包快速入门 在不同的包下面创建不同的Dog类 275. 包命名 276. 常用的包 一个包下,包含很多的类,java 中常用的包有: java.lang.* //lang 包是基本包&#xff0c;默认引入&…

【英语】5. 考研英语语法体系

文章目录 前言句字的成分一、常规句型简单句&#xff08;5 种&#xff09;1. 定义&#xff1a;句子中只包含 *一套主谓结构* 的句子。&#xff08;一个句子只能有一个谓语动词&#xff09;2. 分类 并列句&#xff08;由关联词组成&#xff09;&#xff08;3 种&#xff09;基本…

Unity Shader Graph基础包200+节点及术语解释

目录 Master Stack: Vertex block&#xff1a; Fragment block​编辑 Alpha Clip Threshold Dither transparency Graph Inspector Texture 2d Array/Texture 3d Virtual Texture Sampler State Keywords Boolean keyword 右键显示所有节点 简化测试系列节点&#x…

[SpringBoot] 苍穹外卖--面试题总结--上

前言 1--苍穹外卖-SpringBoot项目介绍及环境搭建 详解-CSDN博客 2--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;一&#xff09;-CSDN博客 3--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;二&#xff09;-CSDN博客 4--苍穹外码-SpringBoot项目中分类管理 详…

ThreeJS入门(091):THREE.PositionalAudio 知识详解,示例代码

作者&#xff1a; 还是大剑师兰特 &#xff0c;曾为美国某知名大学计算机专业研究生&#xff0c;现为国内GIS领域高级前端工程师&#xff0c;CSDN知名博主&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;webgl&#xff0c;ThreeJS&#xff0c;canvas&#xf…

二叉树—相关结构

1.相关的结构问题&#xff08;分治递归&#xff09; 1.1节点个数 1.2叶子结点个数 叶子结点&#xff1a;没有孩子的节点 1.3树的高度&#xff08;深度&#xff09; 1.4二叉树第k层的节点个数 1.5二叉树查找值为x的节点 2.二叉树的创建和销毁 2.1二叉树的构建 二叉树遍历_牛客…

mysql语句执行过程

具体流程如下: 1】当客户端的SOL发送到MySQL时&#xff0c;首先是到达服务器层的连接器&#xff0c;连接器会对你此次发起的连接进行权限校验&#xff0c;以此来获取你这个账号拥有的权限。当你的账号或密码不正确时&#xff0c;会报用户错误。连接成功如果后续没有任何操作&am…

xlwings直接读取使用区域的数据

这里的读取方法&#xff0c;不用最大行号最大列号&#xff0c;直接使用used_range对应的方法 代码如下&#xff1a; import xlwings as xwfile_path r...\AAA.xlsx# 实例化app&#xff0c;打开文件 app_e xw.App(VisibleFalse,add_bookFalse) work_book appe.books.open(f…

New Phytologist | 中国科学院地理所:菌根共生途径塑造树木根经济空间

本文首发于“生态学者”微信公众号&#xff01; 根经济空间是环境驱动下一系列根属性复杂互作的结果&#xff0c;本质上反映了植物地下碳&#xff08;“货币”&#xff09;投资与养分&#xff08;“商品”&#xff09;收益的多维策略权衡关系&#xff08;图1&#xff09;。然而…

【java】数据类型与变量以及操作符

各位看官&#xff1a;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 目录 一.字面变量&#xff1a; 二&#xff1a;数据类型 1.1&#xff1a;int类型&#xff1a;&#xff0…

高速机器人的点动与直线运动

工业机器人中的点动和直线运动非常之重要&#xff0c;接下来说一下他们的实现过程。 点动&#xff1a; 点动包括两个部分&#xff0c;第一个点动是每一个关节电机的点动&#xff0c;第二个是机器末端向xyz的三个方向进行点动处理。 第一个点动是非常简单的&#xff0c;即把对…