34、Linux内核中PCI设备驱动开发指南

Linux内核中PCI设备驱动开发指南

1. PCI核心功能概述

PCI核心在Linux内核中承担着诸多重要任务,它负责创建和初始化系统中总线、设备以及桥接器的数据结构树,处理总线和设备编号,创建设备条目并提供 proc/sysfs 信息。同时,它还为PCI BIOS和从设备(端点)驱动提供服务,并在硬件支持的情况下提供热插拔支持。此外,它还负责查询(EP)驱动接口,并初始化枚举过程中发现的相应设备,提供MSI中断处理框架和PCI Express端口总线支持,这些功能极大地促进了Linux内核中设备驱动的开发。

2. PCI数据结构

Linux内核的PCI框架基于两个主要数据结构来辅助PCI设备驱动的开发: struct pci_dev struct pci_driver

2.1 struct pci_dev

struct pci_dev 用于表示内核中的每个PCI设备,它描述了设备并存储了一些状态参数。该结构在 include/linux/pci.h 中定义,部分代码如下:

struct pci_dev {
    struct pci_bus    *bus; /* Bus this device is on */
    struct pci_bus *subordinate; /* Bus this device bridges to */
    struct proc_dir_entry *procent;
    struct pci_slot *slot;
    unsigned short vendor;
    unsigned short device;
    unsigned short subsystem_vendor;
    unsigned short subsystem_device;
    unsigned int class;
    u8 revision;     /* PCI revision, low byte of class word */
    u8 hdr_type; /* PCI header type (multi' flag masked out) */
    u8 pin;                /* Interrupt pin this device uses */
    struct pci_driver *driver; /* Driver bound to this device */
    u64 dma_mask;
    struct device_dma_parameters dma_parms;
    struct device dev;
    int cfg_size;
    unsigned int irq;
    unsigned int no_msi:1; /* May not use MSI */
    unsigned int no_64bit_msi:1; /* May only use 32-bit MSIs */
    unsigned int msi_enabled:1;
    unsigned int msix_enabled:1;
    atomic_t enable_cnt;
    ...
};

以下是部分成员的含义:
- procent /proc/bus/pci/ 中的设备条目。
- slot :设备所在的物理插槽。
- vendor :设备制造商的供应商ID,由PCI特殊兴趣小组维护全球注册表,制造商需申请唯一编号,该ID存储在设备配置空间的16位寄存器中。
- device :设备被探测后用于标识该特定设备的ID,依赖于供应商,无官方注册表,也存储在16位寄存器中。
- irq :该字段值得关注。设备启动时,MSI(-X)模式未启用,直到通过 pci_alloc_irq_vectors() API(旧驱动使用 pci_enable_msi() )显式启用。初始时, irq 对应默认预分配的非MSI IRQ,其值或用法可能根据以下情况改变:
- 在MSI中断模式下(成功调用 pci_alloc_irq_vectors() 并设置 PCI_IRQ_MSI 标志),该字段的预分配值将被新的MSI向量替换,向量X(索引从0开始)对应的IRQ号为 pci_dev->irq + X
- 在MSI - X中断模式下(成功调用 pci_alloc_irq_vectors() 并设置 PCI_IRQ_MSIX 标志),该字段的预分配值不变,但在该模式下 irq 无效,使用它请求服务中断可能导致不可预测的行为。

成员 含义
procent /proc/bus/pci/ 中的设备条目
slot 设备所在的物理插槽
vendor 设备制造商的供应商ID
device 设备被探测后的特定ID
irq 中断号,根据不同中断模式有不同表现
2.2 struct pci_device_id

struct pci_device_id 用于识别设备,其定义如下:

struct pci_device_id {
    u32 vendor, device;
    u32 subvendor, subdevice;
    u32 class, class_mask;
    kernel_ulong_t driver_data;
};

各成员含义如下:
- vendor device :分别表示设备的供应商ID和设备ID,配对形成设备的唯一32位标识符,驱动依赖此标识符识别设备。
- subvendor subdevice :表示子系统ID。
- class class_mask :与类相关的PCI驱动使用,用于处理给定类的所有设备,对于此类驱动, vendor device 应设置为 PCI_ANY_ID
- driver_data :驱动私有数据,不用于识别设备,用于传递不同数据以区分设备。

创建 struct pci_device_id 实例有三个宏:
- PCI_DEVICE :用于描述特定PCI设备,将子供应商、子设备和类相关字段设置为 PCI_ANY_ID
- PCI_DEVICE_CLASS :用于描述特定PCI设备类,将供应商、设备、子供应商和子设备字段设置为 PCI_ANY_ID 。例如: PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EX PRESS, 0xffffff) 对应NVMe设备的PCI类。
- PCI_DEVICE_SUB :用于描述带有子系统的特定PCI设备。

以下是一个示例数组:

static const struct pci_device_id
bt8xxgpio_pci_tbl[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879) },
    { 0, },
};

使用 MODULE_DEVICE_TABLE 宏将这些信息导出到用户空间,示例如下:

MODULE_DEVICE_TABLE(pci, bt8xxgpio_pci_tbl);
2.3 struct pci_driver

struct pci_driver 表示PCI设备驱动的实例,每个PCI驱动都必须创建并填充该结构才能在核中注册。其定义如下:

struct pci_driver {
    const char *name;
    const struct pci_device_id *id_table;
    int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
    void (*remove)(struct pci_dev *dev);
    int (*suspend)(struct pci_dev *dev, pm_message_t state);
    int (*resume)(struct pci_dev *dev); /* Device woken up */
    void (*shutdown) (struct pci_dev *dev);
    ...
};

各成员含义如下:
- name :驱动的名称,必须在内核中所有PCI驱动中唯一,通常设置为驱动模块的名称,如果同名驱动已注册,则注册将失败。
- id_table :指向前面描述的 struct pci_device_id 表,不能为空才能调用 probe 函数。
- probe :驱动探测函数的指针,当PCI设备与驱动的 id_table 中的条目匹配时,由PCI核心调用,如果成功初始化设备返回0,否则返回负错误码。
- remove :当设备从系统中移除或驱动从内核卸载时,由PCI核心调用。
- suspend resume shutdown :可选但推荐的电源管理函数,可以使用PCI相关的电源管理辅助函数。

以下是一个初始化示例:

static struct pci_driver bt8xxgpio_pci_driver = {
    .name = " bt8xxgpio",
    .id_table = bt8xxgpio_pci_tbl,
    .probe = bt8xxgpio_probe,
    .remove = bt8xxgpio_remove,
    .suspend = bt8xxgpio_suspend,
    .resume = bt8xxgpio_resume,
};
3. PCI驱动注册

注册PCI驱动需要调用 pci_register_driver() ,并传入 struct pci_driver 结构的指针,通常在模块的初始化方法中完成,示例如下:

static int init pci_foo_init(void) {
    return pci_register_driver(&bt8xxgpio_pci_driver);
}

pci_register_driver() 注册成功返回0,否则返回负错误码。在模块卸载时,需要调用 pci_unregister_driver() 来注销驱动,示例如下:

static void exit pci_foo_exit(void) {
    pci_unregister_driver(&bt8xxgpio_pci_driver);
}

为了方便,PCI核心提供了 module_pci_driver() 宏来自动处理注册和注销,示例如下:

module_pci_driver(bt8xxgpio_pci_driver);
4. PCI驱动结构概述及设备操作
4.1 启用设备

在对PCI设备进行任何操作之前,必须显式启用该设备,可使用 pci_enable_device() 函数,示例代码如下:

int err;
err = pci_enable_device(pci_dev); 
if (err) {
    printk(KERN_ERR " foo_dev: Can' t enable device.\n");
    return err;
}

pci_enable_device() 会初始化内存映射和I/O BARs。如果只需要初始化其中一种,可以使用 pci_enable_device_mem() pci_enable_device_io() 。禁用设备时,使用 pci_disable_device() ,示例如下:

void pci_disable_device(struct pci_dev *dev)
4.2 总线主控能力

PCI设备成为总线主控时可发起总线事务,启用总线主控即启用设备的DMA功能,可使用 pci_set_master() 函数,禁用则使用 pci_clear_master() 函数:

void pci_set_master(struct pci_dev *dev)
void pci_clear_master(struct pci_dev *dev)
4.3 访问配置寄存器

驱动需要访问设备的配置空间,以读取必要信息或设置重要参数。内核提供了不同大小数据的读写API,示例如下:

// 读取配置空间数据
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);

// 写入配置空间数据
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

// 获取设备修订ID示例
static unsigned char foo_get_revision(struct pci_dev *dev) {
    u8 revision;
    pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
    return revision;
}
4.4 访问内存映射I/O资源

访问内存映射I/O资源通常需要先请求内存区域,再进行映射。可使用 request_mem_region() ioremap() 函数,示例如下:

struct resource *request_mem_region (unsigned long start, unsigned long n, const char *name)
void iomem *ioremap(unsigned long phys_addr, unsigned long size);

// 映射bar0示例
unsigned long bar0_base; 
unsigned long bar0_size;
void iomem *bar0_map_membase;
bar0_base = pci_resource_start(pdev, 0);
bar0_size = pci_resource_len(pdev, 0);

if (request_mem_region(bar0_base, bar0_size, " bar0-mapping")) {
    goto err_disable;
}

bar0_map_membase = ioremap(bar0_base, bar0_size);
if (!bar0_map_membase) {
    goto err_iomap;
}

PCI框架还提供了许多辅助函数,简化了这些操作:

int pci_request_region(struct pci_dev *pdev, int bar, const char *res_name)
int pci_request_regions(struct pci_dev *pdev, const char *res_name)
void iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen)
void iomem *pci_iomap_range(struct pci_dev *dev, int bar, unsigned long offset, unsigned long maxlen)
void iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar)
void pci_iounmap(struct pci_dev *dev, void iomem *addr)
void pci_release_regions(struct pci_dev *pdev)

以下是使用辅助函数映射BAR1的示例:

#define DRV_NAME " foo-drv"
void iomem *bar1_map_membase;
int err;
err = pci_request_regions(pci_dev, DRV_NAME);
if (err) {
    goto error;
}
bar1_map_membase = pci_iomap(pdev, 1, 0);
if (!bar1_map_membase) {
    goto err_iomap;
}
5. 访问I/O端口资源

访问I/O端口资源需要经过请求I/O区域、映射I/O区域(可选)和访问I/O区域这几个步骤。 pci_requestregion*() pci_iomap*() 函数可处理I/O端口和I/O内存,它们根据资源标志调用相应的底层辅助函数。访问I/O端口的API如下:

u8 inb(unsigned long port);
u16 inw(unsigned long port);
u32 inl(unsigned long port);
void outb(u8 value, unsigned long port);
void outw(u16 value, unsigned long port);
void outl(u32 value, unsigned long port);
6. 处理中断

需要处理设备中断的驱动通常在 probe() 方法中请求中断。对于更通用的方法,推荐使用 pci_alloc_irq_vectors() 函数,示例如下:

int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, unsigned int max_vecs, unsigned int flags);

该函数根据 flags 参数可以处理传统中断、MSI或MSI - X中断。可能的标志定义在 include/linux/pci.h 中:
- PCI_IRQ_LEGACY :单个传统IRQ向量。
- PCI_IRQ_MSI :成功时, pci_dev->msi_enabled 设置为1。
- PCI_IRQ_MSIX :成功时, pci_dev->msix_enabled 设置为1。
- PCI_IRQ_ALL_TYPES :尝试按固定顺序分配上述任何一种中断,优先尝试MSI - X模式。
- PCI_IRQ_AFFINITY :允许自动分配中断亲和力。

获取Linux IRQ编号可使用 pci_irq_vector() 函数:

int pci_irq_vector(struct pci_dev *dev, unsigned int nr);

以下是处理中断的mermaid流程图:

graph TD
    A[开始] --> B[调用pci_alloc_irq_vectors]
    B --> C{是否成功}
    C -- 是 --> D{中断模式}
    C -- 否 --> E[处理错误]
    D -- MSI-X --> F[使用pci_dev->msix_enabled判断]
    D -- MSI --> G[使用pci_dev->msi_enabled判断]
    D -- 传统 --> H[使用pci_dev->irq]
    F --> I[获取对应IRQ编号]
    G --> J[获取对应IRQ编号]
    H --> K[使用pci_dev->irq]
    I --> L[请求中断]
    J --> L
    K --> L
    L --> M[处理中断]
    M --> N[结束]
    E --> N
7. 传统INTx IRQ分配及相关问题处理

PCI总线类型的探测方法 pci_device_probe() 会调用 pci_assign_irq() pcibios_alloc_irq() 来分配IRQ。 pci_assign_irq() 读取设备连接的引脚,并根据PCI主机桥的 .map_irq 回调函数创建IRQ映射。

需要注意的是, PCI_INTERRUPT_LINE 中的IRQ值在调用 pci_enable_device() 之前是无效的,且外设驱动不应更改该值。

为了解决大多数PCIe设备在传统INTx模式下共享INTA输入的问题,采用了“虚拟线INTx IRQ交换”技术,在 pci_device_probe() 调用的 pci_assign_irq() 函数中有相关处理。

8. 锁定考虑

许多设备驱动在中断处理程序中使用每个设备的自旋锁。在基于Linux的系统中,处理基于引脚的中断或单个MSI时,不需要禁用中断。但如果设备使用多个中断,驱动在持有锁时必须禁用中断,可使用 spin_lock_irqsave() spin_lock_irq() 函数。

9. 旧API说明

一些旧驱动仍使用已弃用的MSI或MSI - X API,如 pci_enable_msi() pci_disable_msi() 等,新代码不应使用这些API。以下是一个尝试使用MSI并在失败时回退到传统中断模式的示例:

int err;
/* 尝试使用MSI中断 */
err = pci_enable_msi(pci_dev);
if (err) {
    goto intx;
}
err = devm_request_irq(&pci_dev->dev, pci_dev->irq, my_msi_handler, 0, " foo-msi", priv);
if (err) {
    pci_disable_msi(pci_dev);
    goto intx;
}
return 0;

/* 尝试使用传统中断 */
intx:
dev_warn(&pci_dev->dev, " Unable to use MSI interrupts, falling back to legacy\n");
err = devm_request_irq(&pci_dev->dev, pci_dev->irq, ...);

综上所述,开发Linux内核中的PCI设备驱动需要深入理解PCI核心功能、数据结构以及各种操作方法,合理使用相关API和辅助函数,同时注意中断处理、锁定和旧API的使用问题,这些步骤和技巧对于成功开发PCI设备驱动至关重要。

Linux内核中PCI设备驱动开发指南

10. 总结与最佳实践

在开发Linux内核中的PCI设备驱动时,我们需要遵循一系列的步骤和最佳实践,以确保驱动的正确性和性能。以下是一个总结性的列表,涵盖了从设备初始化到中断处理的关键步骤:

  1. 驱动注册
    • 创建并初始化 struct pci_driver 结构。
    • 使用 pci_register_driver() 进行注册,使用 pci_unregister_driver() 进行注销,推荐使用 module_pci_driver() 宏自动处理。
  2. 设备启用
    • 在操作设备前,使用 pci_enable_device() 启用设备,根据需要使用 pci_enable_device_mem() pci_enable_device_io()
    • 使用 pci_disable_device() 禁用设备。
  3. 总线主控能力
    • 若设备需要进行DMA操作,使用 pci_set_master() 启用总线主控,使用 pci_clear_master() 禁用。
  4. 配置寄存器访问
    • 使用 pci_read_config_*() pci_write_config_*() 函数访问设备配置空间。
  5. 内存映射I/O资源访问
    • 使用 request_mem_region() ioremap() 或PCI框架提供的辅助函数请求和映射内存区域。
    • 使用 ioread*() iowrite*() 访问映射的寄存器。
  6. I/O端口资源访问
    • 使用 pci_requestregion*() pci_iomap*() 函数处理I/O端口请求和映射。
    • 使用 in*() out*() 函数访问I/O端口。
  7. 中断处理
    • 使用 pci_alloc_irq_vectors() 分配中断向量。
    • 使用 pci_irq_vector() 获取Linux IRQ编号。
  8. 锁定考虑
    • 若设备使用多个中断,在中断处理程序中使用 spin_lock_irqsave() spin_lock_irq() 禁用中断并获取锁。
操作步骤 相关函数
驱动注册 pci_register_driver() pci_unregister_driver() module_pci_driver()
设备启用 pci_enable_device() pci_enable_device_mem() pci_enable_device_io() pci_disable_device()
总线主控能力 pci_set_master() pci_clear_master()
配置寄存器访问 pci_read_config_*() pci_write_config_*()
内存映射I/O资源访问 request_mem_region() ioremap() pci_request_region() pci_iomap()
I/O端口资源访问 pci_requestregion*() pci_iomap*() in*() out*()
中断处理 pci_alloc_irq_vectors() pci_irq_vector()
锁定考虑 spin_lock_irqsave() spin_lock_irq()
11. 代码示例整合

以下是一个完整的PCI设备驱动开发示例,整合了上述的关键步骤:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>

#define DRV_NAME "foo-drv"

// 定义pci_device_id表
static const struct pci_device_id bt8xxgpio_pci_tbl[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878) },
    { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879) },
    { 0, },
};
MODULE_DEVICE_TABLE(pci, bt8xxgpio_pci_tbl);

// 定义probe函数
static int bt8xxgpio_probe(struct pci_dev *dev, const struct pci_device_id *id) {
    int err;
    void iomem *bar1_map_membase;

    // 启用设备
    err = pci_enable_device(dev);
    if (err) {
        dev_err(&dev->dev, "Can't enable device\n");
        return err;
    }

    // 启用总线主控
    pci_set_master(dev);

    // 请求并映射内存区域
    err = pci_request_regions(dev, DRV_NAME);
    if (err) {
        dev_err(&dev->dev, "Can't request regions\n");
        goto error;
    }

    bar1_map_membase = pci_iomap(dev, 1, 0);
    if (!bar1_map_membase) {
        dev_err(&dev->dev, "Can't map BAR1\n");
        goto err_iomap;
    }

    // 分配中断向量
    err = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_ALL_TYPES);
    if (err < 0) {
        dev_err(&dev->dev, "Can't allocate IRQ vectors\n");
        goto err_irq;
    }

    // 请求中断
    err = devm_request_irq(&dev->dev, pci_irq_vector(dev, 0), my_irq_handler, 0, DRV_NAME, dev);
    if (err) {
        dev_err(&dev->dev, "Can't request IRQ\n");
        goto err_irq;
    }

    return 0;

err_irq:
    pci_iounmap(dev, bar1_map_membase);
err_iomap:
    pci_release_regions(dev);
error:
    pci_disable_device(dev);
    return err;
}

// 定义remove函数
static void bt8xxgpio_remove(struct pci_dev *dev) {
    pci_iounmap(dev, pci_iomap(dev, 1, 0));
    pci_release_regions(dev);
    pci_disable_device(dev);
}

// 定义pci_driver结构
static struct pci_driver bt8xxgpio_pci_driver = {
    .name = DRV_NAME,
    .id_table = bt8xxgpio_pci_tbl,
    .probe = bt8xxgpio_probe,
    .remove = bt8xxgpio_remove,
};

// 模块初始化函数
static int __init pci_foo_init(void) {
    return pci_register_driver(&bt8xxgpio_pci_driver);
}

// 模块退出函数
static void __exit pci_foo_exit(void) {
    pci_unregister_driver(&bt8xxgpio_pci_driver);
}

module_init(pci_foo_init);
module_exit(pci_foo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PCI device driver example");
12. 性能优化与调试建议

在开发PCI设备驱动时,性能优化和调试是非常重要的环节。以下是一些建议:

12.1 性能优化
  • 减少内存访问次数 :尽量批量读取和写入数据,避免频繁的单次访问。
  • 合理使用中断 :根据设备特性选择合适的中断模式,避免不必要的中断开销。
  • 优化DMA操作 :确保DMA缓冲区的合理分配和使用,提高数据传输效率。
12.2 调试建议
  • 使用内核日志 :使用 printk() 函数输出调试信息,注意日志级别。
  • 使用调试工具 :如 gdb kgdb 等进行内核调试。
  • 检查硬件连接 :确保PCI设备的物理连接正常,避免硬件故障导致的问题。
13. 未来发展趋势

随着硬件技术的不断发展,PCI设备驱动开发也面临着新的挑战和机遇。以下是一些未来可能的发展趋势:

  • 更高的带宽和速度 :PCIe标准不断演进,未来的设备将支持更高的带宽和数据传输速度,驱动需要适应这些变化。
  • 更多的功能和特性 :新的PCI设备可能会具备更多的功能和特性,如智能电源管理、安全加密等,驱动需要支持这些新功能。
  • 软件定义硬件 :软件定义硬件的趋势将使得PCI设备的配置和管理更加灵活,驱动需要提供相应的接口和支持。
14. 结论

开发Linux内核中的PCI设备驱动是一个复杂而又富有挑战性的任务。通过深入理解PCI核心功能、数据结构和操作方法,遵循最佳实践,合理使用相关API和辅助函数,我们可以开发出高效、稳定的PCI设备驱动。同时,关注性能优化、调试技巧和未来发展趋势,将有助于我们更好地应对不断变化的硬件环境和需求。希望本文能够为开发者提供一个全面的指南,帮助他们在PCI设备驱动开发的道路上取得成功。

以下是开发PCI设备驱动的整体mermaid流程图:

graph TD
    A[开始] --> B[驱动注册]
    B --> C[设备启用]
    C --> D[总线主控启用]
    D --> E[访问配置寄存器]
    E --> F[访问内存映射I/O资源]
    F --> G[访问I/O端口资源]
    G --> H[分配中断向量]
    H --> I[请求中断]
    I --> J[处理中断]
    J --> K[设备操作]
    K --> L[设备禁用]
    L --> M[驱动注销]
    M --> N[结束]
根据原作 https://pan.quark.cn/s/459657bcfd45 的源码改编 Classic-ML-Methods-Algo 引言 建立这个项目,是为了梳理和总结传统机器学习(Machine Learning)方法(methods)或者算法(algo),和各位同仁相互学习交流. 现在的深度学习本质上来自于传统的神经网络模型,很大程度上是传统机器学习的延续,同时也在不少时候需要结合传统方法来实现. 任何机器学习方法基本的流程结构都是通用的;使用的评价方法也基本通用;使用的一些数学知识也是通用的. 本文在梳理传统机器学习方法算法的同时也会顺便补充这些流程,数学上的知识以供参考. 机器学习 机器学习是人工智能(Artificial Intelligence)的一个分支,也是实现人工智能最重要的手段.区别于传统的基于规则(rule-based)的算法,机器学习可以从数据中获取知识,从而实现规定的任务[Ian Goodfellow and Yoshua Bengio and Aaron Courville的Deep Learning].这些知识可以分为四种: 总结(summarization) 预测(prediction) 估计(estimation) 假想验证(hypothesis testing) 机器学习主要关心的是预测[Varian在Big Data : New Tricks for Econometrics],预测的可以是连续性的输出变量,分类,聚类或者物品之间的有趣关联. 机器学习分类 根据数据配置(setting,是否有标签,可以是连续的也可以是离散的)和任务目标,我们可以将机器学习方法分为四种: 无监督(unsupervised) 训练数据没有给定...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值