Анализ примера V4L2 — подробное объяснение исходного кода vivi.c (хорошая подробная статья)
Анализ примера V4L2 — подробное объяснение исходного кода vivi.c (хорошая подробная статья)

1. Ядро драйвера V4L2:

Исходный код драйвера V4L2 находится в каталоге driver/media/video. Основные основные коды:

  • v4l2-dev.c: интерфейс захвата видео Linux версии 2, регистрация основной структуры video_device;
  • v4l2-common.c:существовать Linux Операционная система использует низкоуровневую операционную систему для управления набором устройств. struct tures/vectors Универсальный интерфейс видеоустройства;
  • v4l2-device.c: поддержка устройств V4L2, зарегистрируйте v4l2_device;
  • v4l2-ioctl.c: обработка V4L2 из ioctl Заказизодининдивидуальный Универсальныйизрамка;
  • v4l2-subdev.c: подустройство v4l2;
  • v4l2-mem2mem.c: память в память Linux и videobuf Израмка видеооборудования, оборудование из вспомогательных функций, использование его источника из videobuf буфер.

Если мы посмотрим непосредственно на исходный код драйвера, у нас все еще нет понимания структуры драйвера, особенно платформы V4L2, которая очень сложна. Давайте сначала проанализируем ее на основе виртуального видеодрайвера vivi.c, включенного в ядро. исходный код, версия ядра 3.4.2.

2. Анализ исходного кода виртуального видеодрайвера vivi.c

2.1. Анализируйте программу, начиная с ее функции входа в инициализацию:

Язык кода:javascript
копировать
static int __init vivi_init(void)
{
    const struct font_desc *font = find_font("VGA8x16");
    int ret = 0, i;
 
    if (font == NULL) {
        printk(KERN_ERR "vivi: could not find font\n");
        return -ENODEV;
    }
    font8x16 = font->data;
 
    if (n_devs <= 0)
        n_devs = 1;
 
    for (i = 0; i < n_devs; i++) {
        ret = vivi_create_instance(i);
        if (ret) {
            /* If some instantiations succeeded, keep driver */
            if (i)
                ret = 0;
            break;
        }
    }
 
    if (ret < 0) {
        printk(KERN_ERR "vivi: error %d while loading driver\n", ret);
        return ret;
    }
 
    printk(KERN_INFO "Video Technology Magazine Virtual Video "
            "Capture Board ver %s successfully loaded.\n",
            VIVI_VERSION);
 
    /* n_devs will reflect the actual number of allocated devices */
    n_devs = i;
 
    return ret;
}
 
static void __exit vivi_exit(void)
{
    vivi_release();
}
 
module_init(vivi_init);
module_exit(vivi_exit);

Определение n_devs идет первым, как показано ниже:

Язык кода:javascript
копировать
static unsigned n_devs = 1;
module_param(n_devs, uint, 0644);
MODULE_PARAM_DESC(n_devs, "numbers of video devices to create");

Написано очень ясно: n_devs представляет количество видеоустройств, которые вы хотите создать.

Примечание. Обычно в пользовательском режиме параметры передаются через основную функцию. Первый параметр представляет количество аргументов, а второй параметр представляет конкретные параметры. В состоянии ядра параметры не могут передаваться таким образом. Обычно используется метод Module_param. Шаги следующие:

  • использовать модуль_парам указывает модуль из параметров;
  • Передавать параметры модулю при загрузке драйвера;

Удалив другие утверждения, мы обнаружили, что важной функцией является только одна функция vivi_create_instance(i). Давайте проанализируем эту функцию ниже.

2.2. Функция vivi_create_instance(i):

Язык кода:javascript
копировать
static int __init vivi_create_instance(int inst)
{
    struct vivi_dev *dev;
    struct video_device *vfd;
    struct v4l2_ctrl_handler *hdl;
    struct vb2_queue *q;
    int ret;
 
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;
 
    snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
            "%s-%03d", VIVI_MODULE_NAME, inst);
    ret = v4l2_device_register(NULL, &dev->v4l2_dev);
    if (ret)
        goto free_dev;
 
    dev->fmt = &formats[0];
    dev->width = 640;
    dev->height = 480;
    hdl = &dev->ctrl_handler;
    v4l2_ctrl_handler_init(hdl, 11);
    dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
    dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
    dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_CONTRAST, 0, 255, 1, 16);
    dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_SATURATION, 0, 255, 1, 127);
    dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_HUE, -128, 127, 1, 0);
    dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
    dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_GAIN, 0, 255, 1, 100);
    dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
    dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
    dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
    dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
    dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);
    dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);
    dev->bitmask = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_bitmask, NULL);
    if (hdl->error) {
        ret = hdl->error;
        goto unreg_dev;
    }
    v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);
    dev->v4l2_dev.ctrl_handler = hdl;
 
    /* initialize locks */
    spin_lock_init(&dev->slock);
 
    /* initialize queue */
    q = &dev->vb_vidq;
    memset(q, 0, sizeof(dev->vb_vidq));
    q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
    q->drv_priv = dev;
    q->buf_struct_size = sizeof(struct vivi_buffer);
    q->ops = &vivi_video_qops;
    q->mem_ops = &vb2_vmalloc_memops;
 
    vb2_queue_init(q);
 
    mutex_init(&dev->mutex);
 
    /* init video dma queues */
    INIT_LIST_HEAD(&dev->vidq.active);
    init_waitqueue_head(&dev->vidq.wq);
 
    ret = -ENOMEM;
    vfd = video_device_alloc();
    if (!vfd)
        goto unreg_dev;
 
    *vfd = vivi_template;
    vfd->debug = debug;
    vfd->v4l2_dev = &dev->v4l2_dev;
    set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
 
    /*
     * Provide a mutex to v4l2 core. It will be used to protect
     * all fops and v4l2 ioctls.
     */
    vfd->lock = &dev->mutex;
 
    ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
    if (ret < 0)
        goto rel_vdev;
 
    video_set_drvdata(vfd, dev);
 
    /* Now that everything is fine, let's add it to device list */
    list_add_tail(&dev->vivi_devlist, &vivi_devlist);
 
    if (video_nr != -1)
        video_nr++;
 
    dev->vfd = vfd;
    v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
         video_device_node_name(vfd));
    return 0;
 
rel_vdev:
    video_device_release(vfd);
unreg_dev:
    v4l2_ctrl_handler_free(hdl);
    v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
    kfree(dev);
    return ret;
}

1), функция сначала struct vivi_dev *dev выделить память, а затем dev->v4l2_dev.name Имя установлено на “vivi-i” форма, а затем позвоните v4l2_device_register Эта функция используется для регистрации dev->v4l2_dev Эта структура, структура v4l2_device Как показано ниже, его имя называется V4L2 устройство, оно должно быть V4L2 Основная структура устройства:

Язык кода:javascript
копировать
struct v4l2_device {
    /* dev->driver_data points to this struct.
       Note: dev might be NULL if there is no parent device
        as is the case with e.g. ISA devices.*/
#if defined(CONFIG_MEDIA_CONTROLLER) 
    struct media_device *mdev;
#endif
    
    /* used to keep track of the registered subdevs */
    struct list_head subdevs;
 
    /* lock this struct, can be used by the drivers as well if this 
       struct is embedded into a larger struct.*/  
    spinlock_t lock;
    
    /* unique device name, by default the driver name + bus ID */
    char name[V4L2_DEVICE_NAME_SIZE];
    
    /* notify callback called by some sub-devices. */
    void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
 
    /* The control handler. May be NULL. */
    struct v4l2_ctrl_handler *ctrl_handler;
 
    /* Device's priority state */
    struct v4l2_prio_state prio;
    
    /* BKL replacement mutex.Temporary solution only. */
    struct kref ref;
    
    /* Release function that is called when the ref count goes to 0. */
    void (*release)(struct v4l2_devcie *v4l2_dev); 
};

Вы можете видеть, что эта структура содержит элемент родительского устройства устройства, заголовок списка дочерних устройств subdevs, блокировку вращения, указатель функции уведомления, дескриптор управления v4l2_ctrl_handler, приоритет prio, счетчик ссылок и указатель функции выпуска. Мы пока не будем проводить детальный анализ этой структуры. Мы проанализируем ее позже, когда будем анализировать фреймворк V4L2.

2), проходит v4l2_devie_register(NULL, &dev->v4l2_dev) функцию завершения регистрации структуры, видно, что в “vivi.c” , его родительское устройство NULL,v4l2_device_register функционировать в v4l2-device.c середина:

Язык кода:javascript
копировать
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
    if (v4l2_dev == NULL)
        return -EINVAL;
 
    INIT_LIST_HEAD(&v4l2_dev->subdevs);
    spin_lock_init(&v4l2_dev->lock);
    mutex_init(&v4l2_dev->ioctl_lock);
    v4l2_prio_init(&v4l2_dev->prio);
    kref_init(&v4l2_dev->ref);
    get_device(dev);
    v4l2_dev->dev = dev;
    if (dev == NULL) {
        /* If dev == NULL, then name must be filled in by the caller */
        WARN_ON(!v4l2_dev->name[0]);
        return 0;
    }
 
    /* Set name to driver name + device name if it is empty. */
    if (!v4l2_dev->name[0])
        snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
            dev->driver->name, dev_name(dev));
    if (!dev_get_drvdata(dev))
        dev_set_drvdata(dev, v4l2_dev);
    return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);

Эта функция завершает инициализацию связанного списка подустройств, блокировки вращения, мьютекса, приоритета и счетчика ссылок. Другая работа не выполняется.

3), продолжайте возвращаться к vivi_create_instance(i) Анализ по функциисередина,Внизодинпредложение:dev->fmt = &formats[0];

Путем прямого поиска обнаружено, что vivi.c поддерживает массив форматов, который представляет форматы данных, поддерживаемые vivi.c. Мы представили формат видео в среде V4L2. Благодаря этой строке кода мы знаем, что vivi.c поддерживает следующий формат: V4L2_PIX_FMT_YUYV.

Язык кода:javascript
копировать
struct vivi_fmt {
    char *name;
    u32 fourcc; /* v4l2 format id */
    int depth;
};
 
static struct vivi_fmt formats[] = {
    {
        .name = "4:2:2, packed, YUYV",
        .fourcc = V4L2_PIX_FMT_YUYV,
        .depth = 16,
    },
    {
        .name = "4:2:2, packed, UYVY",
        .fourcc = V4L2_PIX_FMT_UYVY,
        .depth = 16,
    },
    {
        .name = "RGB565 (LE)",
        .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
        .depth = 16,
    },
    {
        .name = "RGB565 (BE)",
        .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
        .depth = 16,
    },
    {
        .name = "RGB555 (LE)",
        .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
        .depth = 16,
    },
    {
        .name = "RGB555 (BE)",
        .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
        .depth = 16,
    },
};

4) Продолжайте анализировать в функции vivi_create_instance(i):

Язык кода:javascript
копировать
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);

Структура v4l2_ctrl_handler определена в v4l2-ctrls.h следующим образом:

Язык кода:javascript
копировать
struct v4l2_ctrl_handler {
    struct mutex lock;
    struct list_head ctrls;
    struct list_head ctrl_refs;
    struct v4l2_ctrl_ref *cached;
    struct v4l2_ctrl_ref ** buckets;
    u16 nr_of_buckets;
    int error;
};

v4l2_ctrl_handler — это структура, используемая для сохранения набора методов управления суб-устройствами. Для видеоустройств эти элементы управления включают настройку яркости, насыщенности, контрастности и резкости и т. д. Элементы управления сохраняются в связанном списке. Вы можете добавить элементы управления в связанный. list через функцию v4l2_ctrl_new_std. Эта функция используется в коде ниже.

Язык кода:javascript
копировать
/* Initialize the handler */
int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned nr_of_controls_hint)
{
    mutex_init(&hdl->lock);
    INIT_LIST_HEAD(&hdl->ctrls);
    INIT_LIST_HEAD(&hdl->ctrl_refs);
    hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8;
    hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]), GFP_KERNEL);
    hdl->error = hdl->buckets ? 0 : -ENOMEM;
    return hdl->error;
}

Вычислите nr_of_buckets по размеру переменной nr_of_control_hint, вычислите nr_of_buckets, подайте заявку на пространство для бакетов и сохраните результат приложения в переменной ошибки.

5) Продолжайте анализировать в функции vivi_create_instance(i) и продолжайте устанавливать некоторые другие параметры в структуре dev. При настройке таких параметров, как громкость, яркость, контрастность, насыщенность и т. д., вызывается функция v4l2_ctrl_new_std, а для кнопки int32, устанавливается меню, битовая маска и другие параметры и вызывается функция v4l2_ctrl_new_custom. С первого взгляда можно сказать, что это две функции. Функции интерфейса, предоставляемые платформой V4L2.

Язык кода:javascript
копировать
struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s32 min, s32 max, u32 step, s32 def);
  • hdl да инициализировалось хорошо из v4l2_ctrl_handler структура;
  • ops да v4l2_ctrl_ops структура, содержащая ctrls из Конкретная реализация;
  • id дапроходить IOCTL из arg Параметры передаются команде из и определяются существующим v4l2-controls.h документ;
  • min、max Используется для определения объема операции. Например: v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0);

Пользовательское пространство может вызывать v4l2_ctrl_handler с помощью инструкции VIDIOC_S_CTRL ioctl, а идентификатор передается через параметр arg.

Выполните настройку яркости, насыщенности и т.д. в видео через несколько неясных вопросов.

6) Далее происходит работа буферной очереди, установка некоторых параметров очереди vb2_queue q, наиболее важными из которых являются следующие два параметра:

Язык кода:javascript
копировать
q->ops = &vivi_qops;
q->mem_ops = &vb2_vmalloc_memops;

Можетк Видеть:q->ops = &vivi_video_qops середина vivi_video_qops Это необходимость vivi.c Реализован набор рабочих функций, который vivi.c середина определяется следующим образом:

Язык кода:javascript
копировать
static struct vb2_ops vivi_video_qops = {
    .queue_setup = queue_setup,
    .buf_init = buffer_init,
    .buf_prepare = buffer_prepare,
    .buf_finish = buffer_finish,
    .buf_cleanup = buffer_cleanup,
    .buf_queue = buffer_queue,
    .start_streaming = start_streaming,
    .stop_streaming = stop_streaming,
    .wait_prepare = vivi_unlock,
    .wait_finish = vivi_lock,
};

Эти немногиеиндивидуальныйфункция Это Необходимость Мы реализуем это сами при написании драйвера из.

Чтосередина vb2_ops Структура находится в videobuf2-core.h серединаопределение,Как показано ниже:

Язык кода:javascript
копировать
struct vb2_ops {
    int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
             unsigned int *num_buffers, unsigned int *num_planes,
             unsigned int sizes[], void *alloc_ctxs[]);
 
    void (*wait_prepare)(struct vb2_queue *q);
    void (*wait_finish)(struct vb2_queue *q);
 
    int (*buf_init)(struct vb2_buffer *vb);
    int (*buf_prepare)(struct vb2_buffer *vb);
    int (*buf_finish)(struct vb2_buffer *vb);
    void (*buf_cleanup)(struct vb2_buffer *vb);
 
    int (*start_streaming)(struct vb2_queue *q, unsigned int count);
    int (*stop_streaming)(struct vb2_queue *q);
 
    void (*buf_queue)(struct vb2_buffer *vb);
};

для vb2_vmalloc_memops структура, которая представляет собой videobuf2-vmalloc.c серединаопределение,Как показано ниже:

Язык кода:javascript
копировать
const struct vb2_mem_ops vb2_vmalloc_memops = {
    .alloc        = vb2_vmalloc_alloc,
    .put        = vb2_vmalloc_put,
    .get_userptr    = vb2_vmalloc_get_userptr,
    .put_userptr    = vb2_vmalloc_put_userptr,
    .vaddr        = vb2_vmalloc_vaddr,
    .mmap        = vb2_vmalloc_mmap,
    .num_users    = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);

Посмотрите на его название vb2 Вначале эти функции должны быть хорошими функциями, предоставляемыми системой. Изучив исходный код, мы обнаружили, что они существуют. videobuf2-vmalloc.c середина。

Затем вызовите функцию vb2_queue_init(q) для ее инициализации. Функция vb2_queue_init(q) выглядит следующим образом:

Язык кода:javascript
копировать
/**
 * vb2_queue_init() - initialize a videobuf2 queue
 * @q:        videobuf2 queue; this structure should be allocated in driver
 *
 * The vb2_queue structure should be allocated by the driver. The driver is
 * responsible of clearing it's content and setting initial values for some
 * required entries before calling this function.
 * q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer
 * to the struct vb2_queue description in include/media/videobuf2-core.h
 * for more information.
 */
int vb2_queue_init(struct vb2_queue *q)
{
    BUG_ON(!q);
    BUG_ON(!q->ops);
    BUG_ON(!q->mem_ops);
    BUG_ON(!q->type);
    BUG_ON(!q->io_modes);
 
    BUG_ON(!q->ops->queue_setup);
    BUG_ON(!q->ops->buf_queue);
 
    INIT_LIST_HEAD(&q->queued_list);
    INIT_LIST_HEAD(&q->done_list);
    spin_lock_init(&q->done_lock);
    init_waitqueue_head(&q->done_wq);
 
    if (q->buf_struct_size == 0)
        q->buf_struct_size = sizeof(struct vb2_buffer);
 
    return 0;
}
EXPORT_SYMBOL_GPL(vb2_queue_init);

Он просто выполняет некоторые операторы проверки и оценки, а также инициализирует некоторые связанные списки, спин-блокировки и т. д.

7)、/* init video dma queues */

INIT_LIST_HEAD(&dev->vidq.active);

init_waitqueue_head(&dev->vidq.wq);

8), следующее является правильным video_device издействовать,это имеет значениедаэтотиндивидуальныйфункциясерединаосновнойиздействовать:

Язык кода:javascript
копировать
struct video_device *vfd;
vfd = video_device_alloc();
*vfd = vivi_template;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
video_set_drvdata(vfd, dev);

8.1) Давайте сначала посмотрим на это video_device структура, которая представляет собой v4l2-dev.h серединаопределение,Отображается следующим образом:

Язык кода:javascript
копировать
struct video_device {
    /* device ops */
    const struct v4l2_file_operations *fops;
 
    /* sysfs */
    struct device dev;    /* v4l device */
    struct cdev *cdev;    /* character device */
    
    /* Set either parent or v4l2_dev if your driver uses v4l2_device */
    struct device *parent;    /* device parent */
    struct v4l2_device *v4l2_dev;    /* v4l2_device parent */
    
    /* Control handler associated with this device node. May be NULL */
    struct v4l2_ctrl_handler *ctrl_handler;
 
    /* Priority state. If NULL, then v4l2_dev->prio will be used */
    struct v4l2_prio_state *prio;
 
    /* device info */
    char name[32];
    int vfl_type;
 
    /* 'minor' is set to -1 if the registration failed */
    int minor;
    u16 num;
    
    /* use bitops to set/clear/test flags */
    unsigned long flags;
 
    /* attribute to differentiate multiple indices on one physical device */
    int index;
    
    /* V4L2 file handles */
    spinlock_t fh_lock;    /* Lock for all v4l2_fhs */
    struct list_head fh_list;    /* List of struct v4l2_fh */
    int debug;    /* Activates debug level */
    
    /* Video standed vars */
    v4l2_std_id tvnorms;    /* Supproted tv norms */
    v4l2_std_id current_norm;    /* Current tv norm */
    
    /* callbacks */
    void (*release)(struct video_device *vdev);
    
    /* ioctl callbacks */
    const struct v4l2_ioctl_ops *ioctl_ops;
    
    /* serialization lock */
    struct mutex *lock;
};

Вы должны быть в состоянии примерно понять значение каждого участника на основе комментариев. Позже будут некоторые функции инициализации и регистрации этой функции, которые должны включать в себя установку и инициализацию членов этой структуры, поэтому мы подробно разберем эти члены позже.

8.2), см. ниже video_device_alloc функция. это в v4l2-dev.c серединаопределение:

Язык кода:javascript
копировать
struct video_device *video_device_alloc(void)
{
    return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}
EXPORT_SYMBOL(video_device_alloc);

Он просто выделяет часть памяти и устанавливает для нее значение 0, не устанавливая элементы в структуре video_device.

8.3), тогда vivi.c середина Следующее предложение да *vfd = vivi_template существование vivi.c Поиск показал, что существование было определено ранее:

Язык кода:javascript
копировать
static struct video_device vivi_template = {
    .name = "vivi",
    .fops = &vivi_fops,
    .ioctl_ops = &vivi_ioctl_ops,
    .release = video_device_release,
      
    .tvnorms = V4L2_STD_525_60,
    .current_norm = V4L2_STD_NTSC_M,
};

контраст video_device Члены структуры серединаиз, могут быть определены естьсуществовать Это выполняет задание из. Ему назначены только некоторые члены его середины.

8.3.1)、video_device Структура середина firstda .fops = &vivi_fops,существовать vivi.c поиск середина выглядит так:

Язык кода:javascript
копировать
static const struct v4l2_file_operations vivi_fops = {
    .owner = THIS_MODULE,
    .open = v4l2_fh_open,
    .release = vivi_close,
    .read = vivi_read,
    .poll = vivi_poll,
    .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
    .mmap = vivi_mmap,
};

Давайте сначала посмотрим на имена этих индивидуальных функций. open функция сумма unlocked_ioctl Названия функций отличаются от других. Интуиция мне подсказывает, что они предусмотрены системой, а названия других функций такие. vivi Начиная с того, что этот отдельный файл находится внутри реализации функции, мы существуем. vivi.c Вы можете найти его, выполнив поиск по середина, но мы пока не будем их подробно анализировать.

8.3.2)、video_device Структурасерединавторойиндивидуальныйда .ioctl_ops = &vivi_ioctl_ops , посмотри название тоже дасуществовать vivi.c серединаопределениеиз:

Язык кода:javascript
копировать
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
    .vidioc_querycap = vidioc_querycap,
    .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
    .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
    .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
    .vidioc_reqbufs = vidioc_reqbufs,
    .vidioc_querybuf = vidioc_querybuf,
    .vidioc_qbuf = vidioc_qbuf,
    .vidioc_dqbuf = vidioc_dqbuf,
    .vidioc_s_std = vidioc_s_std,
    .vidioc_enum_input = vidioc_enum_input,
    .vidioc_g_input = vidioc_g_input,
    .vidioc_s_input = vidioc_s_input,
    .vidioc_streamon = vidioc_streamon,
    .vidioc_streamoff = vidioc_streamoff,
    .vidioc_log_status = v4l2_ctrl_log_status,
    .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

Как видите, это множество вызовов ioctl, которые условно можно разделить на следующие категории:

  • Производительность запросов query capability из:vidioc_querycap;
  • истинный формат некоторой операции:vidioc_enum_fmt_vid_cap,vidioc_g_fmt_vid_cap,vidioc_try_fmt_vid_cap,vidioc_s_fmt_vid_cap;
  • верно Буфер из некоторых операций: vidioc_reqbufs, vidioc_querybuf, vidioc_qbuf, vidioc_dqbuf;
  • верно стандартный стандарт работы: vidioc_s_std;
  • верный ввод ввода из операций: vidioc_enum_input, vidioc_g_input, vidiocs_s_input;
  • вернопоток stream из операции:vidioc_streamon,vidioc_streamoff;

Выше ioctl Позвони обоим необходимостьнаша собственная реализация из, позади 3 индивидуальный ioctl имя v4l2 Начиная с из, он должен быть реализован в системе да. Найти существующие можно поиском. v4l2-ctrls.c и v4l2-event.c серединаопределение。

8.3.3)、video_device Структурасерединатретийиндивидуальныйда .release = video_device_release,этосуществовать v4l2-dev.c серединаопределение,Как показано ниже:

Язык кода:javascript
копировать
void video_device_release(struct video_device *vdev)
{
    kfree(vdev);
}
EXPORT_SYMBOL(video_device_release);

8.3.4)、video_device Структурасерединачетвертый,пятьиндивидуальныйда:

Язык кода:javascript
копировать
.tvnorms = V4L2_STD_525_60,

.current_norm = V4L2_STD_NTSC_M,

глядя на video_device Структурасерединаиз Комментарий:

Язык кода:javascript
копировать
/* Video standard vars */
v4l2_std_id tvnorms;    /* Supported tv norms */
v4l2_std_id current_norm;    /* Current tvnorm */

Относится к поддерживаемым ТВ-форматам и текущему ТВ-формату.

8.3.5), анализ завершен vivi_template члены серединаиз, т.е. video_device Члены структуры серединаиз, также немного сбивающие с толку, существуют video_device Структурасередина,иметьодининдивидуальный struct v4l2_file_operation член, который, в свою очередь, включает члена индивидуального unlocked_ioctl, в то же время video_device Структурасередина还иметьодининдивидуальный struct v4l2_ioctl_ops Участник, почему здесь два участника? ioctl Функция-член? Давайте сначала проведем приблизительный анализ,struct v4l2_file_operations vivi_fops середина .unlocked_ioctl = video_ioctl2,этотиндивидуальный video_ioctl2 существовать v4l2-ioctl.c середина:

Язык кода:javascript
копировать
long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2);

оно звонит снова __video_do_ioctl функция,такжесуществовать v4l2-ioctl.c середина,этотиндивидуальный __video_do_ioctl функциядаодининдивидуальныйбольшойиз switch,case заявление, согласно разным случай, вызывая разные функции для VIDIOC_QUERYCAP Например:

Язык кода:javascript
копировать
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
case VIDIOC_QUERYCAP:
    ret = ops->vidioc_querycap(file, fh, cap);

использоватьсуществовать vivi.c этотиндивидуальныйпримерто есть:vfd этотиндивидуальный Структурато есть vivi_template,Ops то есть vivi_template серединаиз ioctl_ops член, а также то есть vivi_ioctl_ops,для VIDIOC_QUERCAP Макрос, то, что на самом деле называется vivi_ioctl_ops серединаиз .vidiioc_querycap члены, то есть мы существуем vivi.c середина пойми это сама из static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) функция. Это немного сбивает с толку, у меня уже кружится голова@__@!~~ Подробно мы проанализируем его позже.

8.4)、продолжатьсуществовать vivi_create_instance серединаанализировать:

Язык кода:javascript
копировать
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;

Обратите внимание на это предложение индивидуальное, отсюда вы можете увидеть регистрацию существования. video_device Вы должны зарегистрироваться заранее v4l2_device.

Язык кода:javascript
копировать
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
vfd->lock = &dev->mutex;

Сделал некоторые настройки, а затем естьбольшой boss Получил:

Язык кода:javascript
копировать
ret = video_register_device(vfd, VFL_TYPE_GRABBER,  video_nr);

Его существование v4l2-dev.h середина определяется следующим образом:

Язык кода:javascript
копировать
static inline int __must_check video_register_device_no_warm(struct video_device *vdev, int type, int nr)
{
    return __video_register_device(vdev, type, nr, 0, vdev->fops->owner);
}

__video_register_device существовать v4l2-dev.c серединаопределение:(Просто прямосуществоватькодсередина Комментарий Понятно)

Язык кода:javascript
копировать
/**
 *    __video_register_device - register video4linux devices
 *    @vdev: video device structure we want to register
 *    @type: type of device to register
 *    @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ...
 * -1 == first free)
 *    @warn_if_nr_in_use: warn if the desired device node number
 *     was already in use and another number was chosen instead.
 *    @owner: module that owns the video device node
 *
 *    The registration code assigns minor numbers and device node numbers
 *    based on the requested type and registers the new device node with
 *    the kernel.
 *
 *    This function assumes that struct video_device was zeroed when it
 *    was allocated and does not contain any stale date.
 *
 *    An error is returned if no free minor or device node number could be
 *    found, or if the registration of the device node failed.
 *
 *    Zero is returned on success.
 *
 *    Valid types are
 *
 *    %VFL_TYPE_GRABBER - A frame grabber
 *
 *    %VFL_TYPE_VBI - Vertical blank data (undecoded)
 *
 *    %VFL_TYPE_RADIO - A radio card
 *
 *    %VFL_TYPE_SUBDEV - A subdevice
 */
int __video_register_device(struct video_device *vdev, int type, int nr,
        int warn_if_nr_in_use, struct module *owner)
{
    int i = 0;
    int ret;
    int minor_offset = 0;
    int minor_cnt = VIDEO_NUM_DEVICES;
    const char *name_base;
 
    /* A minor value of -1 marks this video device as never
     having been registered */
    vdev->minor = -1;
 
    /* the release callback MUST be present */
    if (WARN_ON(!vdev->release))
        return -EINVAL;
/* Если эта индивидуальная функция выпуска не указана, ошибка будет возвращена напрямую, и она предоставляется существующим vivi_templateсередина. */
    /* v4l2_fh support */
    spin_lock_init(&vdev->fh_lock);
    INIT_LIST_HEAD(&vdev->fh_list);
 
    /* Part 1: check device type */
    switch (type) {
    case VFL_TYPE_GRABBER:
        name_base = "video";
        break;
    case VFL_TYPE_VBI:
        name_base = "vbi";
        break;
    case VFL_TYPE_RADIO:
        name_base = "radio";
        break;
    case VFL_TYPE_SUBDEV:
        name_base = "v4l-subdev";
        break;
    default:
        printk(KERN_ERR "%s called with unknown type: %d\n",
         __func__, type);
        return -EINVAL;
    }
/* В соответствии с переданным параметром изtype определите имя из в каталоге существования устройства/dev. */
    vdev->vfl_type = type;
    vdev->cdev = NULL;
    if (vdev->v4l2_dev) {
        if (vdev->v4l2_dev->dev)
            vdev->parent = vdev->v4l2_dev->dev;
        if (vdev->ctrl_handler == NULL)
            vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
        /* If the prio state pointer is NULL, then use the v4l2_device
         prio state. */
        if (vdev->prio == NULL)
            vdev->prio = &vdev->v4l2_dev->prio;
    }
/* Выполните обработку родительского устройства vdevseедина иctrl. Функция инициализации. */
    /* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* Keep the ranges for the first four types for historical
     * reasons.
     * Newer devices (not yet in place) should use the range
     * of 128-191 and just pick the first free minor there
     * (new style). */
    switch (type) {
    case VFL_TYPE_GRABBER:
        minor_offset = 0;
        minor_cnt = 64;
        break;
    case VFL_TYPE_RADIO:
        minor_offset = 64;
        minor_cnt = 64;
        break;
    case VFL_TYPE_VBI:
        minor_offset = 224;
        minor_cnt = 32;
        break;
    default:
        minor_offset = 128;
        minor_cnt = 64;
        break;
    }
#endif
 
    /* Pick a device node number */
    mutex_lock(&videodev_lock);
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
    if (nr == minor_cnt)
        nr = devnode_find(vdev, 0, minor_cnt);
    if (nr == minor_cnt) {
        printk(KERN_ERR "could not get a free device node number\n");
        mutex_unlock(&videodev_lock);
        return -ENFILE;
    }
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 1-on-1 mapping of device node number to minor number */
    i = nr;
#else
    /* The device node number and minor numbers are independent, so
     we just find the first free minor number. */
    for (i = 0; i < VIDEO_NUM_DEVICES; i++)
        if (video_device[i] == NULL)
            break;
    if (i == VIDEO_NUM_DEVICES) {
        mutex_unlock(&videodev_lock);
        printk(KERN_ERR "could not get a free minor\n");
        return -ENFILE;
    }
#endif
    vdev->minor = i + minor_offset;
    vdev->num = nr;
    devnode_set(vdev);
 
    /* Should not happen since we thought this minor was free */
    WARN_ON(video_device[vdev->minor] != NULL);
    vdev->index = get_index(vdev);
    mutex_unlock(&videodev_lock);
/* Выше изpart2to есть определение устройства по номеру субустройства */
    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc();
    if (vdev->cdev == NULL) {
        ret = -ENOMEM;
        goto cleanup;
    }
/* Чтобы зарегистрировать устройство, используйте функцию cdev_alloc. Отсюда мы видим, что это обычный драйвер символьного устройства, а затем задайте для него некоторые параметры. Как это есть Драйвер символьного устройства? ? ? Давайте поговорим об этом позже. */
    vdev->cdev->ops = &v4l2_fops;
/* cdevвнутри структурыopsзаостренныйv4l2_fopsэтотиндивидуальный Структура,этотиндивидуальныйv4l2_fopsСтруктуратакжедасуществоватьv4l2-dev.cэтотиндивидуальныйдокументсередина。сноваодининдивидуальныйfile_operationsдействоватьфункциянабор,существоватьvivi.cсерединаиметьодининдивидуальныйv4l2_file_operations vivi_fops, какая между ними связь? */
    vdev->cdev->owner = owner;
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    if (ret < 0) {
        printk(KERN_ERR "%s: cdev_add failed\n", __func__);
        kfree(vdev->cdev);
        vdev->cdev = NULL;
        goto cleanup;
    }
 
 
    /* Part 4: register the device with sysfs */
    vdev->dev.class = &video_class;
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
    if (vdev->parent)
        vdev->dev.parent = vdev->parent;
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
    ret = device_register(&vdev->dev);
    if (ret < 0) {
        printk(KERN_ERR "%s: device_register failed\n", __func__);
        goto cleanup;
    }
    /* Register the release callback that will be called when the last
     reference to the device goes away. */
    vdev->dev.release = v4l2_device_release;
 
    if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
        printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
            name_base, nr, video_device_node_name(vdev));
 
    /* Increase v4l2_device refcount */
    if (vdev->v4l2_dev)
        v4l2_device_get(vdev->v4l2_dev);
/* существующегоsysfsсередина создает класс и создает узел устройства в рамках существующего класса. */
 
#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Part 5: Register the entity. */
    if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
     vdev->vfl_type != VFL_TYPE_SUBDEV) {
        vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
        vdev->entity.name = vdev->name;
        vdev->entity.info.v4l.major = VIDEO_MAJOR;
        vdev->entity.info.v4l.minor = vdev->minor;
        ret = media_device_register_entity(vdev->v4l2_dev->mdev,
            &vdev->entity);
        if (ret < 0)
            printk(KERN_WARNING
             "%s: media_device_register_entity failed\n",
             __func__);
    }
#endif
/* Создайте объект сущности. Этот шаг не является обязательным. Этот шаг необходимо настроить после настройки параметра CONFIG_MEDIA_CONTROLLER. Этот шаг содержит структуру объекта индивидуальныйmedia_entity, которая будет проанализирована позже. */
    /* Part 6: Activate this minor. The char device can now be used. */
    set_bit(V4L2_FL_REGISTERED, &vdev->flags);
/* Установить бит флага */
    mutex_lock(&videodev_lock);
    video_device[vdev->minor] = vdev;
/* Сохраните набор структуры vdev изvideo_device в массив video_device середина по младшему номеру устройства. Этот отдельный массив дасуществовать переднюю статику struct video_device *video_device[VIDEO_NUM_DEVICES];Определение из. */
    mutex_unlock(&videodev_lock);
 
    return 0;
 
cleanup:
    mutex_lock(&videodev_lock);
    if (vdev->cdev)
        cdev_del(vdev->cdev);
    devnode_clear(vdev);
    mutex_unlock(&videodev_lock);
    /* Mark this video device as never having been registered. */
    vdev->minor = -1;
    return ret;
}
EXPORT_SYMBOL(__video_register_device);

8.5), регистрация завершена video_device Продолжить после структуры vivi_create_instance серединаосуществлять:

Язык кода:javascript
копировать
/* Воля vivi_dev dev добавить в video_device vfd середина, зачем ты это делаешь? да для интерфейса драйвера символьного устройства после кизиспользовать */
video_set_drvdata(vfd, dev);
 
/* добавить в device list связанный списоксередина */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
 
/* для подсчета */
if(video_nr != -1)
    video_nr ++;
 
/* ассоциация video_device и vivi_dev */
dev->vfd = vfd;

3. Анализ и резюме:

На этом мы закончили анализ vivi_init и vivi_create_instance Функция, vivi.c середина, оставшаяся из кода, в основном естьк Вниз 3 Мы не будем сейчас анализировать конкретный код реализации индивидуальной структуры.

Язык кода:javascript
копировать
static struct video_device vivi_template = {
    .name        = "vivi",
    .fops = &vivi_fops,
    .ioctl_ops     = &vivi_ioctl_ops,
    .release    = video_device_release,
    .tvnorms = V4L2_STD_525_60,
    .current_norm = V4L2_STD_NTSC_M,
};
 
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
    .vidioc_querycap = vidioc_querycap,
    .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
    .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
    .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
    .vidioc_reqbufs = vidioc_reqbufs,
    .vidioc_querybuf = vidioc_querybuf,
    .vidioc_qbuf = vidioc_qbuf,
    .vidioc_dqbuf = vidioc_dqbuf,
    .vidioc_s_std = vidioc_s_std,
    .vidioc_enum_input = vidioc_enum_input,
    .vidioc_g_input = vidioc_g_input,
    .vidioc_s_input = vidioc_s_input,
    .vidioc_streamon = vidioc_streamon,
    .vidioc_streamoff = vidioc_streamoff,
    .vidioc_log_status = v4l2_ctrl_log_status,
    .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
 
static const struct v4l2_file_operations vivi_fops = {
    .owner        = THIS_MODULE,
    .open = v4l2_fh_open,
    .release = vivi_close,
    .read = vivi_read,
    .poll        = vivi_poll,
    .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
    .mmap = vivi_mmap,
};

Давайте сначала проанализируем этого человека vivi.c Цель состоит в том, чтобы бросить приблизительный взгляд на некоторые из v4l2 Код драйвера, оставьте несколько вопросов для последующего анализа v4l2 из рамки кода и некоторых концепций иногда, также для этого человека vivi.c Проиллюстрирую примерами. Проанализировав общую структуру, продолжим ее детальный анализ. vivi.c середина Эти конкретные коды реализованы.

Пример описания:

к read Например, приложение существует, вызывает read когда, в соответствии с драйвером file_operations v4l2_fops серединаиз v4l2_read функция,существоватьфункциявпроходить ret = vdev->fops->read(filp, buf, sz, off) Последний звонок нам существует vivi.c середина Заявка на регистрациюиз video_device vivi_template внутри структуры fops->read функция, то есть vivi_read функция. Прямо сейчас V4L2 Только рамка обеспечивает индивидуальную середину передачи эффекта. Посмотрите еще раз vivi_read Внутри функции: возврат vb2_read(&dev->vb_vidq, data, count, ppos, file->f_flags & O_NONBLOCK);оно звонит снова videobuf2-core.c серединаиз vb2_read функция. Это действительно показывает v4l2 рамкаизсерединаэффект поворота.

К таким подобным функциям относятся чтение, запись, опрос, mmap, выпуск Подождите, что еще более особенное ioctl Функция существует, проанализируем ее позже. Все это вызовы приложений, V4L2 рамкасередина Перейти к верному следует издрайвера середина, а затем водитель вызывается в соответствии с разными из, выберите вызов videobuf или ioctl серединаизфункция.

для const struct v4l2_ioctl_ops *ioctl_ops,существовать vivi.c серединато есть static const struct v4l2_ioctl_ops vivi_ioctl_ops;этотиндивидуальный ioctl Более хлопотно, во-первых, как драйвер символьного устройства, когда приложение вызывает ioctl когда это называется file_operations v4l2_fops серединаиз .unlocked_ioctl = v4l2_ioctl,этотиндивидуальный v4l2_ ioctl Также прошло ret = vdev->fops->ioctl(filp, cmd, arg) Только что позвонил vivi.c середина Заявка на регистрациюиз struct video_device vivi_template внутри структуры fops->unlocked_ioctl функция, то есть v4l2_file_operatios vivi_fops внутри video_ioctl2 функция,этотиндивидуальный video_ioctl2 Функция вызывается снова __video_do_ioctl функция(к Последние дваиндивидуальныйфункция Всесуществовать v4l2-ioctl.c середина), в зависимости от cmd Макро, к VIDIOC_QUERYCAP Например:

проходить ret = ops->vidioc_querycap(file, fh, cap);Чтосередина struct video_device *vfd = video_devdata(file);const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;Можетквидеть,__video_do_ioctl Функция вызывается снова Понятно struct video_device vivi_template Структура Лимана .ioctl_ops = &vivi_ioctl_ops,Затем выберите структуру v4l2_ioctl_ios vivi_ioctl_ops на основе имени макроса.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose