linux-5.10.110内核源码分析 - 写磁盘(从VFS系统调用到I/O调度及AHCI写磁盘)

news2025/4/7 14:52:12

1、VFS写文件到page缓存(vfs_write)

1.1、写裸盘(dd)

        使用如下命令写裸盘:

dd if=/dev/zero of=/dev/sda bs=4096 count=1 seek=1

1.2、系统调用(vfs_write)

        系统调用栈如下:

        对于调用栈的new_sync_write函数,buf为写磁盘的内容的内存地址,也就是从/dev/zero读取上来的数据(全0),len为数据长度,也就是4096(块大小为4096,共一个块,也就是4096 bytes),*ppos为偏移,也就是跳过1个bs(4096)。

        wirte系统调用的数据地址及长度保存到了iovec里面,偏移保存到了kiocb:

1.3、写文件缓存(mapping)

        块设备也是一个文件,以4k page为单位对块设备内容进行缓存,页缓存保存在mapping里面,写块设备的时候,如果有对应的缓存则写该page缓存,如果没有则创建page缓存。

1.4、写块设备位置(__generic_file_write_iter)

        前面wirte系统调用介绍了,写偏移保存到了kiocb里面,最终在__generic_file_write_iter读取并传递给下一级函数,代码如下:

        这个pos的单位是byte,当前dd命令写的4096偏移。

1.5、查找page缓存(find_get_entry)

        前面介绍了文件系统以4k page为单位缓存文件内容,将一个物理硬盘划分为n个page,wirte系统调用传递了写偏移,偏移pos除以page大小也就是page索引(内核用移位代替除法),代码如下:

        根据page的索引index获取page,代码如下:

        函数调用栈如下:

        (之前没有读该page也没有写该page,当前返回空指针)

1.6、创建page缓存(add_to_page_cache_lru)

        pagecache_get_page获取page缓存失败的情况,会创建一个page,并添加到cache的lru里面,也就是前面的mapping->i_pages,代码如下:

        函数调用栈如下:

        __add_to_page_cache_locked会将page的index设置为offset,这里的offset是写偏移offset对应的page索引(*ppos对应的page索引,后面不再用pos,写磁盘以页为单位,pos不一定以page对齐,如果pos不以page对齐,并且没有缓存,那么写操作会先从磁盘读取一页上来,与当前写的数据合并),后面内核代码都以page索引来写磁盘。

1.7、写缓存(iov_iter_copy_from_user_atomic)

        拷贝比较简单,主要就是将前面传递过来的iov_iter里面的数据拷贝到查找或者创建的page缓存里面,代码如下:

        函数调用栈:

2、写page缓存到bio(blkdev_writepage)

        写page过程,主要通过PageDirty判断文件的page缓存是否为脏(是否被修改,前面写page的时候会标记对应的page为dirty)

2.1、判断page缓存是否dirty(PageDirty)

        write_cache_pages函数主要就是遍历文件缓存,找到dirty的page,然后写磁盘,判断dirty代码如下:

2.2、创建初始化bio(submit_bh_wbc)

        (前面写数据的时候,会调用attach_page_private给page设置一个buffer_head,在__block_write_begin_int里面会根据page->index计算page对应的扇区,扇区、块设备相关信息会保存到buffer_head里面,前面忽略了,这里简要解释一下,因为submit_bh_wbc会用到这些信息)

        submit_bh_wbc将块设备及扇区信息保存到bio的代码如下:

        submit_bh_wbc函数调用栈:

2.3、添加page到bio(__bio_add_page)

        添加page过程比较简单,主要就是添加page到bio->bi_io_vec,bio->bi_io_vec是一个数组,一个个page追加到数组末尾,当前只写一个page,不涉及合并操作,__bio_add_page代码如下:

        __bio_add_page调用栈如下:

3、提交bio(blk_mq_submit_bio)

3.1、获取request请求(__blk_mq_alloc_request)

        __blk_mq_alloc_request调用blk_mq_get_ctx获取当前线程上下文,调用blk_mq_map_queue映射请求到硬件队列,调用blk_mq_get_tag尝试从硬件队列中获取一个可用的标签,用于标识请求,这个tag标签不是硬件相关的,而是一个软件上唯一的tag标签。(这个标签跟/sys/class/block/sda/queue/nr_requests I/O 请求队列中最大并发请求数的值有关,允许最多nr_requests个tag,就是允许最多同时下发nr_requests请求,具体可以查看blk_mq_update_nr_requests、blk_mq_get_tag,__blk_mq_alloc_request函数也可以看到,获取不到tag的时候,__blk_mq_alloc_request重复尝试获取tag)

        __blk_mq_alloc_request相关代码如下:

       获取到tag之后, __blk_mq_alloc_request调用blk_mq_rq_ctx_init分配并初始化一个request。

3.2、bio转request(blk_mq_bio_to_request)

        blk_mq_bio_to_request主要就是将bio的扇区地址以及bio等保存到request里面,代码如下:

3.3、将request添加到plug的mq_list(blk_add_rq_to_plug)

        (blk_mq_submit_bio有很多路径,不同场景走的分支不一样,本文仅针对当前实际场景)

        blk_mq_submit_bio调用blk_add_rq_to_plug将请求先添加到当前进程的plug队列里面,代码如下:

        函数调用栈如下:

3.4、刷request(blk_finish_plug)

        在将所有page经过一系列操作转request保存到plug之后,调用blk_finish_plug,刷当前进程的plug:

        blk_finish_plug最终调用blk_mq_flush_plug_list将plug里面的request发送到不同的队列,代码如下:

void blk_mq_flush_plug_list(struct blk_plug *plug, bool from_schedule)
{
    // 创建一个临时链表头用于存储任务队列
    LIST_HEAD(list);

    // 如果当前插件的多队列链表为空,则直接返回
    if (list_empty(&plug->mq_list))
        return;

    // 将插件中的任务队列移动到临时链表中,并清空原链表
    list_splice_init(&plug->mq_list, &list);

    // 如果任务数量大于2且支持多个队列,则对任务进行排序
    if (plug->rq_count > 2 && plug->multiple_queues)
        list_sort(NULL, &list, plug_rq_cmp);

    // 初始化任务计数器
    plug->rq_count = 0;

    // 循环处理临时链表中的任务
    do {
        // 创建一个局部链表头,用于存储当前批次的任务
        struct list_head rq_list;

        // 获取当前链表的第一个任务,并将其从链表中移除
        struct request *rq, *head_rq = list_entry_rq(list.next);
        struct list_head *pos = &head_rq->queuelist; /* 跳过第一个任务 */
        struct blk_mq_hw_ctx *this_hctx = head_rq->mq_hctx;
        struct blk_mq_ctx *this_ctx = head_rq->mq_ctx;
        unsigned int depth = 1;

        // 遍历剩余的任务,将属于同一硬件上下文和上下文的任务归为一批
        list_for_each_continue(pos, &list) {
            rq = list_entry_rq(pos);
            BUG_ON(!rq->q); // 确保任务队列不为空
            if (rq->mq_hctx != this_hctx || rq->mq_ctx != this_ctx)
                break;
            depth++;
        }

        // 将当前批次的任务从链表中切分出来
        list_cut_before(&rq_list, &list, pos);

        // 跟踪任务未插队事件,记录队列、深度以及是否来自调度程序
        trace_block_unplug(head_rq->q, depth, !from_schedule);

        // 将当前批次的任务插入调度队列
        blk_mq_sched_insert_requests(this_hctx, this_ctx, &rq_list,
                        from_schedule);
    } while (!list_empty(&list)); // 如果链表不为空,则继续处理
}

        (说明:代码注释由chartGPT自动生成)

4、request插入到调度队列(blk_mq_sched_insert_requests)

        (SATA调度的请求没有经过blk_mq_ctx队列,请求直接存入了blk_mq_hw_ctx->queue->elevator->elevator_data里面。)

4.1、将request插入到Deadline调度器(deadline_add_rq_rb)

        当前dd写的sata硬盘,使用deadline调度算法;deadline_add_rq_rb将前面blk_mq_flush_plug_list获取的request插入到Deadline调度器,elv_rb_add函数代码如下:

/**
 * elv_rb_add - 将请求插入到红黑树中,并保持排序顺序。
 * @root: 红黑树的根节点指针。
 * @rq: 要插入的请求节点。
 *
 * 该函数实现了一个典型的红黑树插入操作,同时根据请求的位置进行排序。
 */
void elv_rb_add(struct rb_root *root, struct request *rq)
{
    // 定义一个指向红黑树根节点的指针变量 p,初始值为 root 的子节点指针。
    struct rb_node **p = &root->rb_node;
    
    // 定义一个指向当前父节点的指针变量 parent,用于记录插入位置的上一级节点。
    struct rb_node *parent = NULL;
    
    // 定义一个临时变量 __rq,用于存储当前遍历到的红黑树节点对应的请求。
    struct request *__rq;

    // 开始遍历红黑树,找到合适的插入位置。
    while (*p) { 
        // 将当前节点赋值给 parent,以便后续插入时作为新节点的父节点。
        parent = *p; 
        
        // 将当前节点转换为请求结构体,方便比较请求的顺序。
        __rq = rb_entry(parent, struct request, rb_node); 

        // 如果当前请求的扇区号小于待插入请求的扇区号,则向左子树继续查找。
        if (blk_rq_pos(rq) < blk_rq_pos(__rq)) 
            p = &(*p)->rb_left; 
        else if (blk_rq_pos(rq) >= blk_rq_pos(__rq)) 
            // 否则,向右子树继续查找。
            p = &(*p)->rb_right; 
    }

    /**
     * 插入新节点:
     * - rb_link_node() 用于将新节点插入到红黑树中,但不调整颜色。
     * - rb_insert_color() 用于调整红黑树的颜色属性,确保其仍然是有效的红黑树。
     */
    rb_link_node(&rq->rb_node, parent, p); 
    rb_insert_color(&rq->rb_node, root);
}

         (说明:代码注释由chartGPT自动生成;函数里面的blk_rq_pos是获取扇区地址,本质就是按扇区地址对request排序,对于机械硬盘,按扇区顺序写对性能比较友好,避免太频繁来回移动磁头;当前场景下只有一个请求,需要调试排队的情况,可以用fio下发多个随机写请求)

        函数调用栈如下:

4.2、将request插入到fifo队列末尾(dd_insert_request)

        dd_insert_request插入request的代码如下,会给请求加上一个expire time,jiffies可以理解为当前时间,dd->fifo_expire[data_dir]可以理解为超时时间,也就是从request入队列开始之后的多少时间内应该得到调度,前面的Deadline调度器队列是按扇区排序,一味按扇区顺序下发请求,会导致request长时间得不到调度,所以有一个fifo队列,在不超时的情况下,尽可能按扇区顺序调度request请求,后面可以看到具体的请求过程,这里仅介绍:

        函数调用栈如下:

5、调度request(dd_dispatch_request)

5.1、下一个request请求(deadline_latter_request)

        下一个request请求为在下发一个请求之后,调度器会选择下一个需要调度的请求放到next_rq里面,对于机械硬盘,应该就是下一个最近的扇区的请求,这样可以避免磁头来回跳转,相关函数如下:

static void
deadline_move_request(struct deadline_data *dd, struct request *rq)
{
	const int data_dir = rq_data_dir(rq);

	dd->next_rq[READ] = NULL;
	dd->next_rq[WRITE] = NULL;
	dd->next_rq[data_dir] = deadline_latter_request(rq);

	/*
	 * take it off the sort and fifo list
	 */
	deadline_remove_request(rq->q, rq);
}

        每次下发都需要先清除下一个读写请求,再选择当前请求的下一个请求,并将已经调度的请求从相关队列里面删除。

5.2、批量调度request(__dd_dispatch_request)

        __dd_dispatch_request有两种调度,一种是批量调度,另一种是fifo调度。批量调度就是,如果之前是调度的是写请求,那么接下来还是继续调度写请求,如果之前调度的是读请求,那么接下来还是继续调度读请求,除非fifo有请求超时或者批量请求超过上限等其他情况;fifo调度就是请求超过了应该调度的时间,这个时候优先处理超时的fifo请求。

        __dd_dispatch_request步骤为获取deadline_latter_request选取的下一个请求(只能是读写请求里面的一个或者空,虽然先查询的是WRITE,不表示优先下发写请求,而是如果READ不为空的情况下,WRITE必然为空,先查询哪个都一样,都是由前一个调度的请求决定),代码如下:

    /*
     * 批处理当前只允许读或写,不能同时处理
     */
    rq = deadline_next_request(dd, WRITE); // 尝试获取下一个写请求
    if (!rq) // 如果没有写请求,则尝试获取读请求
        rq = deadline_next_request(dd, READ);

        如果批量调度的请求数量少于批量下发的请求数量,继续调度前面选出的下一个请求,这里是继续前面按扇区排序选择的下一个扇区的请求:

    // 如果有请求并且尚未达到批处理限制,则继续批处理
    if (rq && dd->batching < dd->fifo_batch)
        goto dispatch_request;

        批量调度次数加1,选择下个需要调度的请求,返回当前调度的请求:

dispatch_request: // 标签:调度请求
    /*
     * rq 是选定的合适请求
     */
    dd->batching++; // 增加批处理计数器
    deadline_move_request(dd, rq); // 移动请求到调度队列

done: // 标签:处理完成
    /*
     * 如果请求需要锁定目标区域,则执行锁定操作
     */
    blk_req_zone_write_lock(rq); // 锁定目标区域
    rq->rq_flags |= RQF_STARTED; // 设置请求已启动标志
    return rq; // 返回选定的请求

        批量调度超过上限的情况或者没有下一个请求等情况(磁盘没发送过请求,当前为第一个请求),如果读fifo队列不为空,检查写是否被饿死(写操作前面有多个fifo读操作),如果没有被饿死则调度读操作,否则调度写操作:

    // 如果有读请求且写请求未被饿死,则选择读请求
    if (reads) {
        BUG_ON(RB_EMPTY_ROOT(&dd->sort_list[READ])); // 确保读排序列表不为空

        // 如果写请求未被饿死且已经连续等待超过阈值,则跳转到写请求处理
        if (deadline_fifo_request(dd, WRITE) &&
            (dd->starved++ >= dd->writes_starved))
            goto dispatch_writes;

        data_dir = READ; // 设置数据方向为读

        goto dispatch_find_request; // 跳转到查找请求的标签
    }

5.3、查找下一个request请求(dispatch_find_request)

        批量调度的情况,下发一个请求的时候已经根据磁盘扇区选择好了下一个需要调度的请求,不需要查找,非批量调度的情况下需要选择下一个要调度的请求,前面代码会根据条件选择好是调度读还是调度写,然后从相应队列获取下一个请求。

        选择下一个请求,deadline_next_request获取下一个请求(不考虑zone等情况,这里主要是上一次下发请求是选择的下一个请求,按扇区排序的请求),deadline_check_fifo检查fifo最前面的请求是否超时,如果有超时应该从fifo里面调度超时请求,如果没有超时并且deadline_next_request也没有获取到下一个请求,那么也按fifo调度:

    /*
     * 不在批处理模式下,找到选定数据方向的最佳请求
     */
    next_rq = deadline_next_request(dd, data_dir); // 获取下一个请求
    if (deadline_check_fifo(dd, data_dir) || !next_rq) { // 检查是否有超时请求或无更多请求
        /*
         * 如果有超时请求、上次请求方向不同或已无更高扇区的请求,
         * 则重新从最早到期时间的请求开始。
         */
        rq = deadline_fifo_request(dd, data_dir); // 获取最早的到期请求
    } else {

        没有请求超时,并且按扇区顺序已经选择好了请求的话,调度下一个选择好的请求:

    } else {
        /*
         * 上次请求方向相同且有下一个请求,则继续从这里开始。
         */
        rq = next_rq; // 设置当前请求为下一个请求
    }

        从整个代码看Deadline调度,不保证优先处理超时请求,优先批处理,批处理方向一致,性能较好,批量处理一定程度之后才考虑fifo超时,总体就是尽量保证磁头向一个方向转,避免来回转。至于什么时候触发调度,这里暂时忽略。

        __dd_dispatch_request函数调用栈:

5.4、分发request(__blk_mq_do_dispatch_sched)

        前面的代码选择一个个request,这些request先保存到一个请求链表里面,直到没有请求或者超过最大请求数量:

        __blk_mq_do_dispatch_sched从调度队列获取reqest请求代码如下:

        将调度的request添加到队列:

        至此,系统调用的数据从写缓存到入调度队列再到调度出调度队列已经完成了。request调度之后接下来发送到更下一层。

6、下发request(blk_mq_dispatch_rq_list)

6.1、获取tag(__blk_mq_get_driver_tag)

        在前面《linux-5.10.110内核源码分析 - bcm2711 SATA驱动(AHCI)》里面介绍了,AHCI的一定意义上对应一个slot,也就是一个命令通道,下发request需要给request获取一个slot,这个是硬件相关的,与前面的tag不同,前面的tag可以理解为队列里的一个标签。

        __blk_mq_get_driver_tag获取tag的代码如下:

        函数调用栈:

6.2、request转scsi_cmnd(scsi_prepare_cmd)

        将request转换成scsi_cmnd:

6.3、下发scsi命令(scsi_dispatch_cmd)

        调度队列的请求request保存到了scsi_cmnd,然后调用scsi_dispatch_cmd下发scsi命令,主要就是根据scsi协议,填充各种字段信息,然后scsi再转换成ahci相关命令,最终下发到ahci控制。

        调用栈如下:

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

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

相关文章

arinc818 fpga单色图像传输ip

arinc818协议支持的常用线速率如下图 随着图像分辨率的提高&#xff0c;单lane的速率无法满足特定需求&#xff0c;一种方式是通过多个LANE交叉的去传输图像&#xff0c;另外一种是通过降低图像的带宽&#xff0c;即通过只传单色图像达到对应的效果 程序架构如下图所示&#x…

业务流程先导及流程图回顾

一、测试流程回顾 &#xfeff; 1. 备测内容回顾 &#xfeff; 备测内容: 本次测试涵盖买家和卖家的多个业务流程&#xff0c;包括下单流程、发货流程、搜索退货退款、支付抢购、换货流程、个人中心优惠券等。 2. 先测业务强调 &#xfeff; 1&#xff09;测试业务流程 …

HCIP(RSTP+MSTP)

一、STP的重新收敛&#xff1a; 复习STP接口状态 STP初次收敛至少需要50秒的时间。STP的重新收敛情况&#xff1a; 检测到拓扑变化&#xff1a;当网络中的链路故障或新链路加入时&#xff0c;交换机会检测到拓扑变化。 选举新的根桥&#xff1a;如果原来的根桥故障或与根桥直…

《无线江湖五绝:BLE/WiFi/ZigBee的频谱大战》

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 88万阅读 1.6万收藏 文章目录 **第一回武林大会&#xff0c;群雄并起****第二回WiFi的“降龙十八掌”****第三回BLE的“峨眉轻功”****第四回ZigBee的“暗器百解”****第五回LoRa的“千里传音”****第六回NB…

QT第六课------QT界面优化------QSS

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

C++ STL常用算法之常用算术生成算法

常用算术生成算法 学习目标: 掌握常用的算术生成算法 注意: 算术生成算法属于小型算法&#xff0c;使用时包含的头文件为 #include <numeric> 算法简介: accumulate // 计算容器元素累计总和 fill // 向容器中添加元素 accumulate 功能描述: 计算区间内容器元素…

Tof 深度相机原理

深度相机(TOF)的工作原理_tof相机原理-CSDN博客 深度剖析 ToF 技术&#xff1a;原理、优劣、数据纠错与工业应用全解析_tof技术-CSDN博客 飞行时间技术TOF_tof计算公式-CSDN博客 深度相机&#xff08;二&#xff09;——飞行时间&#xff08;TOF&#xff09;_飞行时间技术-C…

【Linux篇】进程入门指南:操作系统中的第一步

步入进程世界&#xff1a;初学者必懂的操作系统概念 一. 冯诺依曼体系结构1.1 背景与历史1.2 组成部分1.3 意义 二. 进程2.1 进程概念2.1.1 PCB&#xff08;进程控制块&#xff09; 2.2 查看进程2.2.1 使用系统文件查看2.2.2 使⽤top和ps这些⽤⼾级⼯具来获取2.2.3 通过系统调用…

SpringBean模块(一)定义如何创建生命周期

一、介绍 1、简介 在 Spring 框架中&#xff0c;Bean 是指由 Spring 容器 管理的 Java 对象。Spring 负责创建、配置和管理这些对象&#xff0c;并在应用程序运行时对它们进行依赖注入&#xff08;Dependency Injection&#xff0c;DI&#xff09;。 通俗地讲&#xff0c;Sp…

Redis-04.Redis常用命令-字符串常用命令

一.字符串操作命令 set name jack 点击左侧name&#xff0c;显示出值。 get name get abc&#xff1a;null setex key seconds value&#xff1a;设置过期时间&#xff0c;过期后该键值对将会被删除。 然后再get&#xff0c;在过期时间内可以get到&#xff0c;过期get不到。…

Epub转PDF软件Calibre电子书管理软件

Epub转PDF软件&#xff1a;Calibre电子书管理软件 https://download.csdn.net/download/hu5566798/90549599 一款好用的电子书管理软件&#xff0c;可快速导入电脑里的电子书并进行管理&#xff0c;支持多种格式&#xff0c;阅读起来非常方便。同时也有电子书格式转换功能。 …

FAST-LIVO2 Fast, Direct LiDAR-Inertial-Visual Odometry论文阅读

FAST-LIVO2 Fast, Direct LiDAR-Inertial-Visual Odometry论文阅读 论文下载论文翻译FAST-LIVO2: 快速、直接的LiDAR-惯性-视觉里程计摘要I 引言II 相关工作_直接方法__LiDAR-视觉&#xff08;-惯性&#xff09;SLAM_ III 系统概述IV 具有顺序状态更新的误差状态迭代卡尔曼滤波…

【Git】--- Git远程操作 标签管理

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 前面我们学习的操作都是在本地仓库进行了&#xff0c;如果团队内多人协作都在本地仓库操作是不行的&#xff0c;此时需要新的解决方案 --- 远程仓库。…

论文阅读笔记——ST-4DGS,WideRange4D

ST-4DGS ST-4DGS 论文 在 4DGS 中&#xff0c;变形场 F \mathcal{F} F 与运动参数 X 和形状参数 ( S , R ) (S,R) (S,R) 高度耦合&#xff0c;导致训练时高斯表示紧凑型退化&#xff0c;影响动态渲染质量。由此&#xff0c;本文提出两种方法解耦运动与形状参数&#xff0c;保…

[python]基于yolov8实现热力图可视化支持图像视频和摄像头检测

YOLOv8 Grad-CAM 可视化工具 本工具基于YOLOv8模型&#xff0c;结合Grad-CAM技术实现目标检测的可视化分析&#xff0c;支持图像、视频和实时摄像头处理。 功能特性 支持多种Grad-CAM方法实时摄像头处理视频文件处理图像文件处理调用简单 环境要求 Python 3.8需要电脑带有…

豪越科技消防一体化平台:打通消防管理“任督二脉”

在城市的车水马龙间&#xff0c;火灾隐患如潜藏的暗礁&#xff0c;威胁着人们的生命财产安全。传统消防管理模式在现代社会的复杂环境下&#xff0c;逐渐显露出诸多弊端。内部管理分散混乱&#xff0c;人员、装备、物资管理缺乏统一标准和高效流程&#xff1b;外部监管困难重重…

【Matlab】-- 基于MATLAB的美赛常用多种算法

文章目录 文章目录 01 内容概要02 各种算法基本原理03 部分代码04 代码下载 01 内容概要 本资料集合了多种数学建模和优化算法的常用代码资源&#xff0c;旨在为参与美国大学生数学建模竞赛&#xff08;MCM/ICM&#xff0c;简称美赛&#xff09;的参赛者提供实用的编程工具和…

机器学习课程

前言 课程代码和数据文件&#xff1a; 一、机器学习概述 1.1.人工智能概述 机器学习和人工智能&#xff0c;深度学习的关系 机器学习是人工智能的一个实现途径深度学习是机器学习的一个方法发展而来 达特茅斯会议-人工智能的起点 1956年8月&#xff0c;在美国汉诺斯小镇宁静…

AIGC(生成式AI)试用 28 -- 跟着清华教程学习 - AIGC发展研究 3.0

目标&#xff1a;继续学习 - 信息不对称、不平等、隐私泄露和数据滥用 - 问、改、创、优 - “概率预测&#xff08;快速反应&#xff09;”模型和“链式推理&#xff08;慢速思考&#xff09;”模型 - 思维滞环现象解决思路&#xff1a;1.调整提问&#xff1a;改变问题方式&…

问题:md文档转换word,html,图片,excel,csv

文章目录 问题&#xff1a;md文档转换word&#xff0c;html&#xff0c;图片&#xff0c;excel&#xff0c;csv&#xff0c;ppt**主要职责****技能要求****发展方向****学习建议****薪资水平** 方案一&#xff1a;AI Markdown内容转换工具打开网站md文档转换wordmd文档转换pdfm…