Linux内核内存分配
Linux系统使用了一种称为“虚拟内存”的机制。虚拟内存机制使得每个内存地址都是虚拟的,这意味着它们不会直接指向RAM中的任何地址。这样我们访问内存中的存储单元时,都会进行地址转换以匹配相应的物理内存
在Linux系统中,内核中的每个进程都表示为一个task_struct结构体实例,该结构体实例表征并描述了这个进程。在进程开始运行之前,系统会为其分配一个内存映射表,该表存放在struct mm_struct类型的变量中。在内核中,全局变量current时钟指向当前进程,current->mm字段指向当前的进程内存映射表,struct mm_struct结构定义参见include/linux/mm_types.h
地址转换和MMU
MMU不仅可以将虚拟地址转换为物理地址,还可以保护内存免受未经授权的访问。给定一个进程,需要从此进程访问的任何页都必须位于一个VMA中,且必须位于进程的页表中
由于最近访问的数据存放在缓存中,因此最近转换的地址也存放在缓存中。数据缓存加快了数据访问过程,TLB则加快了虚拟地址的转换过程。TLB是内容可寻址内存,其中键是虚拟地址,值是物理地址,其运作过程如下图所示

内存分配机制
下图展示了Linux系统中不同的内存分配器。最低级别的分配器是页分配器,它以页为单位分配内存,然后是Slab分配器,它建立在页分配器的基础上,从中获取页并将它们拆分为较小的内存实体,kmalloc分配器依赖于Slab分配器

实现DMA支持
DMA是计算机系统的一种特性,它允许设备在没有CPU干预的情况下访问主系统内存,使CPU嫩巩固专注于其他任务。它的使用示例包括网络流量加速、音频数据或视频帧抓取等,它的使用并不限于特定领域。负责管理DMA事务的外围设备是DMA控制器,它存在于大多数现代处理器和微控制器中。
DMA的工作方式如下:当驱动程序需要传输数据块时,便使用源地址、目标地址和要复制的总字节数设置DMA控制器,然后DMA控制器自动将数据地址从源地址传输到目标地址,而不会占用CPU周期。当剩余字节数为0时,数据块传输结束并通知驱动程序。
DMA引擎API
DMA控制器接口由两部分组成:控制器和通道。控制器执行内存传输,通道则是客户端驱动程序向控制器提交作业的方式
DMA控制器在Linux内核中别抽象为dma_device结构体实例,其定义如下
struct dma_device {
struct kref ref;
unsigned int chancnt;
unsigned int privatecnt;
struct list_head channels;
struct list_head global_node;
struct dma_filter filter;
dma_cap_mask_t cap_mask;
enum dma_desc_metadata_mode desc_metadata_modes;
unsigned short max_xor;
unsigned short max_pq;
enum dmaengine_alignment copy_align;
enum dmaengine_alignment xor_align;
enum dmaengine_alignment pq_align;
enum dmaengine_alignment fill_align;
#define DMA_HAS_PQ_CONTINUE (1 << 15)
int dev_id;
struct device *dev;
struct module *owner;
struct ida chan_ida;
u32 src_addr_widths;
u32 dst_addr_widths;
u32 directions;
u32 min_burst;
u32 max_burst;
u32 max_sg_burst;
bool descriptor_reuse;
enum dma_residue_granularity residue_granularity;
int (*device_alloc_chan_resources)(struct dma_chan *chan);
int (*device_router_config)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t *src,
unsigned int src_cnt, size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor_val)(
struct dma_chan *chan, dma_addr_t *src, unsigned int src_cnt,
size_t len, enum sum_check_flags *result, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq)(
struct dma_chan *chan, dma_addr_t *dst, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq_val)(
struct dma_chan *chan, dma_addr_t *pq, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf, size_t len,
enum sum_check_flags *pqres, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset)(
struct dma_chan *chan, dma_addr_t dest, int value, size_t len,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset_sg)(
struct dma_chan *chan, struct scatterlist *sg,
unsigned int nents, int value, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
struct dma_chan *chan, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_interleaved_dma)(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_imm_data)(
struct dma_chan *chan, dma_addr_t dst, u64 data,
unsigned long flags);
void (*device_caps)(struct dma_chan *chan,
struct dma_slave_caps *caps);
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
int (*device_pause)(struct dma_chan *chan);
int (*device_resume)(struct dma_chan *chan);
int (*device_terminate_all)(struct dma_chan *chan);
void (*device_synchronize)(struct dma_chan *chan);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*device_issue_pending)(struct dma_chan *chan);
void (*device_release)(struct dma_device *dev);
void (*dbg_summary_show)(struct seq_file *s, struct dma_device *dev);
struct dentry *dbg_dev_root;
};
DMA通道的结构体定义如下
struct dma_chan {
int dev_id;
void __iomem *io;
const char *dev_str;
int irq;
void *irq_dev;
unsigned int fifo_addr;
unsigned int mode;
};
请求DMA通道
dma_request_channel()函数用于请求一个通道
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
配置DMA通道
DMA引擎框架使用struct dma_slave_config数据结构进行配置,该数据结构表示DMA通道的运行时配置,这样客户端就可以指定诸如DMA方向、DMA地址、总线宽度和DMA突发成都等外设的参数,struct dma_slave_config数据结构定义如下
struct dma_slave_config {
enum dma_transfer_direction direction;
phys_addr_t src_addr;
phys_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
u32 src_port_window_size;
u32 dst_port_window_size;
bool device_fc;
void *peripheral_config;
size_t peripheral_size;
};
通过dmaengine_slave_config()函数将这种配置作用于底层硬件上
static inline int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
{
if (chan->device->device_config)
return chan->device->device_config(chan, config);
return -ENOSYS;
}
配置DMA传输
这一步用于确认DMA传输的方式,要进行一次DMA传输,就需要用到与DMA通道对应的控制器中的一些函数,这些函数名为device_prep_dma_*,例如对于内存到内存的传输,使用device_prep_dma_memcpy()
struct dma_async_tx_descriptor *tx;
struct dma_chan *chan = acdev->dma_chan;
dma_cookie_t cookie;
unsigned long flags = DMA_PREP_INTERRUPT;
int ret = 0;
tx = chan->device->device_prep_dma_memcpy(chan, dest, src, len, flags);
if (!tx) {
dev_err(acdev->host->dev, "device_prep_dma_memcpy failed\n");
return -EAGAIN;
}
提交DMA传输
为了把事务放到驱动程序的事务待处理队列中,可以使用dmaengine_submit()函数
static inline dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
{
return desc->tx_submit(desc);
}
发出待处理的DMA请求并等待回调通知
启动传输是DMA传输设置的最后一步,可以通过在通道上调用dma_async_issue_pending()来激活通道待处理队列中的传输。
static inline void dma_async_issue_pending(struct dma_chan *chan)
{
chan->device->device_issue_pending(chan);
}