使用get_user_pages和vmap

时间:2019-04-25 11:42:56

标签: c memory linux-kernel kernel driver

我们有流式摄像机。 API是作为用户模式库实现的,它传递 ioctl 来获取相机信息,抓取视频帧等。

我们还有两个线程-每个线程都有单独的文件指针。一个线程传递命令,另一个线程获取视频帧。从驱动程序的角度来看,某些请求可以并行执行,而其他请求则需要序列化。

在驱动程序中,我们希望将输入和输出用户缓冲区映射到内核空间,以避免使用copy_to_usercopy_from_user函数。

保存视频帧的输出缓冲区可能非常大:超过5 Mb,将跨越近2462页。

这个想法是为我们拥有的每个文件指针分配用户页面存储空间,并映射ioctl中的用户缓冲区。这是代码:

#define PAGES_REQUIRED(size)            (PAGE_ALIGN(size) / PAGE_SIZE)
#define PAGES_ADDR(ptr)                 ((PAGE_ALIGN(ptr) > ptr) ? (PAGE_ALIGN(ptr) - PAGE_SIZE) : ptr)

#define BUFFER_IN_MAX                   10000
#define BUFFER_OUT_MAX                  (FRAMEINFO_SIZEF + MAX_FRAME_SIZE_16)

typedef struct _FILE_CONTEXT
{
    PDEVICE_CONTEXT context;
    struct page *in_pages[(PAGES_REQUIRED(BUFFER_IN_MAX) + 1)];
    struct page *out_pages[(PAGES_REQUIRED(BUFFER_OUT_MAX) + 1) + (PAGES_REQUIRED(BUFFER_IN_MAX) + 1)];
} FILE_CONTEXT, *PFILE_CONTEXT;

struct user_packet
{
    int in_size;
    unsigned char *in_buffer;
    int out_size;
    unsigned char *out_buffer;
};

bool device_user_buffers_share_pages(uint8_t *user_addr_in, size_t user_size_in,
                                    uint8_t *user_addr_out, size_t user_size_out)
{
    uint8_t *user_page_addr_in = PAGES_ADDR((size_t)user_addr_in);
    size_t user_aligned_size_in = user_size_in + (user_addr_in - user_page_addr_in);
    uint8_t *user_page_addr_out = PAGES_ADDR((size_t)user_addr_out);
    size_t user_aligned_size_out = user_size_out + (user_addr_out - user_page_addr_out);
    return ((user_page_addr_in < (user_page_addr_out + user_aligned_size_out)) &&
            (user_page_addr_out < (user_page_addr_in + user_aligned_size_in)));
}

status_t device_map_user_buffer(/*in*/uint8_t *user_addr, /*in*/size_t user_size, /*in*/bool read_only,
                           /*out*/struct page **out_user_pages, /*out*/size_t *out_user_pages_maped,
                           /*out*/uint8_t **out_kernel_base, /*out*/uint8_t **out_kernel_addr)
{
    LogDebug("user_addr: %llu", user_addr);
    LogDebug("user_size: %llu", user_size);
    uint8_t *user_page_addr = PAGES_ADDR((size_t)user_addr);
    LogDebug("user_page_addr: %llu", user_page_addr);
    size_t user_page_offset = user_addr - user_page_addr;
    LogDebug("user_page_offset: %llu", user_page_offset);
    size_t user_pages_required = PAGES_REQUIRED(user_size + user_page_offset);
    LogDebug("user_pages_required: %llu", user_pages_required);
    size_t user_pages_pined = get_user_pages_fast(user_page_addr, user_pages_required, read_only ? 0 : 1, out_user_pages);
    LogDebug("user_pages_pined: %llu", user_pages_pined);
    if (user_pages_pined < user_pages_required) goto error;
    uint8_t *kernel_base = vmap(out_user_pages, user_pages_pined, VM_MAP, PAGE_KERNEL);
    LogDebug("kernel_base: %llu", kernel_base);
    if (!kernel_base) goto error;
    *out_user_pages_maped = user_pages_pined;
    *out_kernel_base = kernel_base;
    *out_kernel_addr = kernel_base + user_page_offset;
    return s_success;
error:
    for (size_t i = 0; i < user_pages_pined; ++i) put_page(out_user_pages[i]);
    return s_fail;
}

void device_unmap_user_buffer(/*in*/struct page **user_pages,
                                 /*in*/size_t user_pages_maped,
                                 /*in*/uint8_t *kernel_base)
{
    vunmap(kernel_base);
    for (size_t i = 0; i < user_pages_maped; ++i) put_page(user_pages[i]);
}

status_t device_open(struct inode *pinode, struct file *pfile)
{
    int minor;
    struct usb_interface *interface;
    PDEVICE_CONTEXT DeviceContext;
    PFILE_CONTEXT FileContext;

    minor = iminor(pinode);
    interface = usb_find_interface(&g_driver, minor);
    if (!interface) return s_dev;
    DeviceContext = usb_get_intfdata(interface);
    if (!DeviceContext) return s_dev;
    FileContext = heap_alloc(sizeof(*FileContext));
    FileContext->context = DeviceContext;
    pfile->private_data = FileContext;
    return s_success;
}

status_t device_close(struct inode *pinode, struct file *pfile)
{
    heap_free(pfile->private_data);
    pfile->private_data = NULL;
    return s_success;
}

long device_ioctl(struct file *pfile, unsigned int IoControlCode, unsigned long arg)
{
    status_t status;
    bool sequential;
    ERROR_CODE errCode;
    size_t szBytestoReturn = 0;
    struct user_packet packet;
    PFILE_CONTEXT FileContext;
    PDEVICE_CONTEXT DeviceContext;
    size_t inSize = 0, outSize = 0;
    uint8_t *inBuf = NULL, *outBuf = NULL;
    size_t inPages = 0, outPages = 0;
    uint8_t *inBase = NULL, *outBase = NULL;

    FileContext = pfile->private_data;
    if (!FileContext) return s_dev;
    DeviceContext = FileContext->context;
    if (!DeviceContext) return s_dev;

    copy_from_user(&packet, (unsigned char*)arg, sizeof(packet));

    // for now we don't test if user buffers actually overlap

    if (packet.in_size > BUFFER_IN_MAX) packet.in_size = BUFFER_IN_MAX;
    if (packet.out_size > BUFFER_OUT_MAX) packet.out_size = BUFFER_OUT_MAX;

    bool in_ok = packet.in_buffer && packet.in_size && access_ok(VERIFY_READ, packet.in_buffer, packet.in_size);
    bool out_ok = packet.out_buffer && packet.out_size && access_ok(VERIFY_WRITE, packet.out_buffer, packet.out_size);
    bool share = in_ok && out_ok && device_user_buffers_share_pages(packet.in_buffer, packet.in_size,
                                                                   packet.out_buffer, packet.out_size);
    if (!share)
    {
        LogDebug("Not shared region {");
        if (in_ok)
        {
            inSize = packet.in_size;
            status = device_map_user_buffer(packet.in_buffer, packet.in_size, true,
                                          FileContext->in_pages, &inPages, &inBase, &inBuf);
            if (!status_success(status))
            {
                LogDebug("device_map_user_buffer failed");
                return status;
            }
        }
        if (out_ok)
        {
            outSize = packet.out_size;
            status = device_map_user_buffer(packet.out_buffer, packet.out_size, false,
                                  FileContext->out_pages, &outPages, &outBase, &outBuf);
            if (!status_success(status))
            {
                LogDebug("device_map_user_buffer failed");
                return status;
            }
        }
        LogDebug("}");
    }
    else
    {
        LogDebug("Shared region {");
        uint8_t *begin = (packet.in_buffer < packet.out_buffer) ? (packet.in_buffer) : (packet.out_buffer);
        uint8_t *end = ((packet.in_buffer + packet.in_size) < (packet.out_buffer + packet.out_size)) ? (packet.out_buffer + packet.out_size) : (packet.in_buffer + packet.in_size);

        inSize = packet.in_size;
        outSize = packet.out_size;
        status = device_map_user_buffer(begin, end - begin, false,
                                       FileContext->out_pages, &outPages, &outBase, &outBuf);
        if (!status_success(status))
        {
            LogDebug("device_map_user_buffer failed");
            return status;
        }

        if (packet.in_buffer < packet.out_buffer)
        {
            inBuf = outBuf;
            outBuf = inBuf + (packet.out_buffer - packet.in_buffer);
        }
        else
        {
            inBuf = outBuf + (packet.in_buffer - packet.out_buffer);
        }
        LogDebug("}");
    }

    // do ioctl like this:
    // switch (IoControlCode)
    // {
    // case A:
    //     errCode = ioctlA(DeviceContext, inBuf, inSize, outBuf, outSize, &szBytestoReturn);
    //     break;
    // }
    // if (errCode == ERR_NO_ERROR) status = szBytestoReturn;
    // else status = GetSystemStatusByErrCode(errCode);

    if (inBase) device_unmap_user_buffer(FileContext->in_pages, inPages, inBase);
    if (outBase) device_unmap_user_buffer(FileContext->out_pages, outPages, outBase);

    return status;
}

当我们发布需要一个用户页面的 ioctl 时,一切正常。但是,当我们尝试捕获多个视频帧时,事情变得很奇怪,最终遇到了分段错误。

我们不使用copy_to_usercopy_from_user函数的原因:

  1. 我们想要在Windows上也能使用的代码。
  2. 必须将16位帧原位转换为8位帧,因此这种方法将导致我们需要为每个帧字调用put_user函数的情况,这会非常慢。

您能帮我理解以上代码的问题吗?

0 个答案:

没有答案