打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
浅析minigui图形引擎的初始化
    浅析minigui图形引擎的初始化

// pdc->surface->pixels就是mmap创建的framebuffer对应的fb_mem起始地址,
// 所以向pdc的cur_dst写入数据,将直接反应到lcd屏幕上[luther.gliethttp]
static void _dc_move_to_1 (PDC pdc, int x, int y)
{
    pdc->cur_dst = (BYTE*)pdc->surface->pixels + pdc->surface->pitch * y;
    pdc->cur_dst += x;
}

InitGUI==>InitGAL
int InitGAL (void)
{
    int i;
    int w, h, depth;
    char engine [LEN_ENGINE_NAME + 1];
    char mode [LEN_MODE + 1];

#ifndef __NOUNIX__
    char* env_value;
    if ((env_value = getenv ("gal_engine"))) { // 环境变量中是否有定义
        strncpy (engine, env_value, LEN_ENGINE_NAME);
        engine [LEN_ENGINE_NAME] = '\0';
    }
    else
#endif
    if (GetMgEtcValue ("system", "gal_engine", engine, LEN_ENGINE_NAME) < 0) {
        // 环境变量中没有定义,那么接下来查找InitMgEtc()加载进来的MiniGUI.cfg配置文件
        // [system]节是否有定义,我的配置文件有该定义,其值为[luther.gliethttp]
        // gal_engine=fbcon或者
        // gal_engine=qvfb
        return ERR_CONFIG_FILE;
    }
    // ok, 这里engine="fbcon"
    if (GAL_VideoInit (engine, 0)) { // 初始化lcd,源码见后.
        GAL_VideoQuit ();
        fprintf (stderr, "NEWGAL: Does not find matched engine: %s.\n", engine);
        return ERR_NO_MATCH;
    }

#ifndef __NOUNIX__
    if ((env_value = getenv ("defaultmode"))) {
        strncpy (mode, env_value, LEN_MODE);
        mode [LEN_MODE] = '\0';
    }
    else
#endif
    if (GetMgEtcValue (engine, "defaultmode", mode, LEN_MODE) < 0)
        if (GetMgEtcValue ("system", "defaultmode", mode, LEN_MODE) < 0)
            return ERR_CONFIG_FILE;
    // 查找InitMgEtc()加载进来的MiniGUI.cfg配置文件
    // defaultmode数值,我的定义为:
    // [fbcon]
    // defaultmode=1024x768-16bpp
    if (!GAL_ParseVideoMode (mode, &w, &h, &depth)) { // 解析"1024x768-16bpp"字符串
        GAL_VideoQuit ();
        fprintf (stderr, "NEWGAL: bad video mode parameter: %s.\n", mode);
        return ERR_CONFIG_FILE;
    }
    // 对于我的配置[luther.gliethttp]
    // w=1024
    // h=768
    // depth=16
    // 设置current_video为w,h和depth
    // 赋值给全局量__gal_screen
    if (!(__gal_screen = GAL_SetVideoMode (w, h, depth, GAL_HWPALETTE))) {
    // Set the requested video mode, allocating a shadow buffer if necessary.
    // 对于每个像素大于8位的video,没有palette[luther.gliethttp]
    // 然后根据w,h,depth这些video属性调用FB_SetVideoMode重新设置
    // video的这3个属性[luther.gliethttp]
    // 这样framebuffer的ubuntu就使用这个新属性来显示了[luther.gliethttp]
    // 同时FB_SetVideoMode将同样设置`
    // current->pixels = mapped_mem+mapped_offset;
        GAL_VideoQuit ();
        fprintf (stderr, "NEWGAL: Set video mode failure.\n");
        return ERR_GFX_ENGINE;
    }

#ifdef _LITE_VERSION
    if (w != __gal_screen->w || h != __gal_screen->h) {
        fprintf (stderr, "The resolution specified in MiniGUI.cfg is not "
                        "the same as the actual resolution: %dx%d.\n"
                        "This may confuse the clients. Please change it.\n",
                         __gal_screen->w, __gal_screen->h);
        GAL_VideoQuit ();
        return ERR_GFX_ENGINE;
    }
#endif

    for (i = 0; i < 17; i++) {
        SysPixelIndex [i] = GAL_MapRGB (__gal_screen->format,
                        SysPixelColor [i].r,
                        SysPixelColor [i].g,
                        SysPixelColor [i].b);
    }
    return 0;
}

int GAL_VideoInit (const char *driver_name, Uint32 flags)
{
    GAL_VideoDevice *video;
    GAL_PixelFormat vformat;
    Uint32 video_flags;
    
    /* Check to make sure we don't overwrite 'current_video' */
    if ( current_video != NULL ) {
        GAL_VideoQuit();
    }
    // 这里engine="fbcon",flags=0
    video = GAL_GetVideo(driver_name); // 根据engine名字获取对应的video驱动结构体[luther.gliethttp]源码见后
   
    if ( video == NULL ) {
        return (-1);
    }
    video->screen = NULL;
    current_video = video; // 当前current_video设置为video

    /* Initialize the video subsystem */
    memset(&vformat, 0, sizeof(vformat));
    if ( video->VideoInit(video, &vformat) < 0 ) { // 调用FB_VideoInit,源码见后[luther.gliethttp]
    // 设置this->hidden->mapped_mem内存映射地址
    // 同时填充RGB分别对应的mask
    // 以及x,y像素数和每像素字节数[luther.gliethttp]
        GAL_VideoQuit();
        return(-1);
    }

    /* Create a zero sized video surface of the appropriate format */
    // #define GAL_SWSURFACE        MEMDC_FLAG_SWSURFACE   /* Surface is in system memory */
    // #define GAL_HWSURFACE        MEMDC_FLAG_HWSURFACE   /* Surface is in video memory */
    // #define GAL_VideoSurface    (current_video->screen)
    // 定义向system内存写入video数据
    video_flags = GAL_SWSURFACE;
    // 源码见后
    // #define GAL_VideoSurface    (current_video->screen) // 设置screen
    GAL_VideoSurface = GAL_CreateRGBSurface(video_flags, 0, 0,
                vformat.BitsPerPixel,
                vformat.Rmask, vformat.Gmask, vformat.Bmask, 0);
    if ( GAL_VideoSurface == NULL ) {
        GAL_VideoQuit();
        return(-1);
    }

    GAL_VideoSurface->video = current_video; // 指定软内存对应的硬件video.[luther.gliethttp]

    video->info.vfmt = GAL_VideoSurface->format; // 获取depth,r,g,b,a的结构体[luther.gliethttp]

    /* We're ready to go! */
    return(0);
}

static VideoBootStrap *bootstrap[] = {
......
#ifdef _NEWGAL_ENGINE_FBCON
    &FBCON_bootstrap, // 名为"fbcon"的驱动
#endif
#ifdef _NEWGAL_ENGINE_QVFB
    &QVFB_bootstrap,
#endif
......
    NULL
};

GAL_VideoDevice *GAL_GetVideo(const char* driver_name)
{
    GAL_VideoDevice *video;
    int index;
    int i;

    index = 0;
    video = NULL;
    if ( driver_name != NULL ) {
        // 我们这里driver_name="fbcon";[luther.gliethttp]
        for ( i=0; bootstrap[i]; ++i ) {
            if ( strncmp(bootstrap[i]->name, driver_name,
                         strlen(bootstrap[i]->name)) == 0 ) {
                if ( bootstrap[i]->available() ) { // 调用FB_Available
                    video = bootstrap[i]->create(index); // 调用FB_CreateDevice
                    break;
                }
            }
        }
    } else {
        // 如果没有定义driver_name,那么
        // 使用第一个可用的video驱动创建一个video.(个人觉得并不推荐)[luther.gliethttp].
        for ( i=0; bootstrap[i]; ++i ) {
            if ( bootstrap[i]->available() ) {
                video = bootstrap[i]->create(index); // new一个video,该video内存将填充对应的video操作方法[luther.gliethttp]
                if ( video != NULL ) {
                    break;
                }
            }
        }
    }
    if (video == NULL)
        return NULL;

    video->name = bootstrap[i]->name; // 该video的名字,我们这里就是"fbcon"[luther.gliethttp]
    /* Do some basic variable initialization */
    video->screen = NULL;
    video->physpal = NULL;
    video->offset_x = 0;
    video->offset_y = 0;
    memset(&video->info, 0, (sizeof video->info));
   

    return video;
}

VideoBootStrap FBCON_bootstrap = {
    "fbcon", "Linux Framebuffer Console",
    FB_Available, FB_CreateDevice
};

static int FB_Available(void)
{
    int console;
    const char *GAL_fbdev;

    GAL_fbdev = getenv("FRAMEBUFFER");
    if ( GAL_fbdev == NULL ) {
        GAL_fbdev = "/dev/fb0";
    }
    console = open(GAL_fbdev, O_RDWR, 0);
    if ( console >= 0 ) {
        // 能打开/dev/fb0,说明linux已经开启framebuffer显示功能,所以返回true[luther.gliehttp]
        close(console);
    }
    return(console >= 0);
}

static GAL_VideoDevice *FB_CreateDevice(int devindex)
{
    GAL_VideoDevice *this;

    /* Initialize all variables that we clean on shutdown */
    this = (GAL_VideoDevice *)malloc(sizeof(GAL_VideoDevice));
    if ( this ) {
        memset(this, 0, (sizeof *this)); // 清0
        this->hidden = (struct GAL_PrivateVideoData *)
                malloc((sizeof *this->hidden));
    }
    if ( (this == NULL) || (this->hidden == NULL) ) {
        GAL_OutOfMemory();
        if ( this ) {
            free(this);
        }
        return(0);
    }
    // 内存空间已经全部申请成功[luther.gliethttp]
    memset(this->hidden, 0, (sizeof *this->hidden));
    // #define wait_vbl     (this->hidden->wait_vbl)
    // #define wait_idle    (this->hidden->wait_idle)
    // #define mouse_fd        (this->hidden->mouse_fd)
    // #define keyboard_fd    (this->hidden->keyboard_fd)
    // #define console_fd    (this->hidden->console_fd)
    wait_vbl = FB_WaitVBL; // linux没有该功能,该函数将直接返回[luther.gliehttp]
    wait_idle = FB_WaitIdle; // 等待设备空闲,对于framebuffer来说,也是直接返回[luther.gliethttp]
    mouse_fd = -1;
    keyboard_fd = -1;

    /* Set the function pointers */
    this->VideoInit = FB_VideoInit; // 设备初始化函数
    this->ListModes = FB_ListModes;
    this->SetVideoMode = FB_SetVideoMode;
    this->SetColors = FB_SetColors;
    this->VideoQuit = FB_VideoQuit;
#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
    this->RequestHWSurface = FB_RequestHWSurface;
#endif
    this->AllocHWSurface = FB_AllocHWSurface;
    this->CheckHWBlit = NULL;
    this->FillHWRect = NULL;
    this->SetHWColorKey = NULL;
    this->SetHWAlpha = NULL;
    this->UpdateRects = NULL;
#if 0
    this->LockHWSurface = FB_LockHWSurface;
    this->UnlockHWSurface = FB_UnlockHWSurface;
    this->FlipHWSurface = FB_FlipHWSurface;
#endif
    this->FreeHWSurface = FB_FreeHWSurface;

    this->free = FB_DeleteDevice;

    return this;
}

static int FB_VideoInit(_THIS, GAL_PixelFormat *vformat)
{
    struct fb_fix_screeninfo finfo;
    struct fb_var_screeninfo vinfo;
    int i;
    int current_index;
    unsigned int current_w;
    unsigned int current_h;
    const char *GAL_fbdev;

    /* Initialize the library */
    GAL_fbdev = getenv("FRAMEBUFFER");
    if ( GAL_fbdev == NULL ) {
        GAL_fbdev = "/dev/fb0";
    }
    console_fd = open(GAL_fbdev, O_RDWR, 0);
    if ( console_fd < 0 ) {
        GAL_SetError("NEWGAL>FBCON: Unable to open %s\n", GAL_fbdev);
        return(-1);
    }

    /* Get the type of video hardware */
    // 获取framebuffer参数,比如我的linux下[luther.gliethttp]
    // /boot/grub/menu.lst
    // 设置的启动参数为vga=0x0317
    // 0x0317代表的意思为,1024*768像素,16位每像素[luther.gliethttp]
    if ( ioctl(console_fd, FBIOGET_FSCREENINFO, &finfo) < 0 ) {
        GAL_SetError("NEWGAL>FBCON: Couldn't get console hardware info\n");
        FB_VideoQuit(this);
        return(-1);
    }
    switch (finfo.type) {
        case FB_TYPE_PACKED_PIXELS:
            /* Supported, no worries.. */
            break;
#ifdef VGA16_FBCON_SUPPORT
        case FB_TYPE_VGA_PLANES:
            /* VGA16 is supported, but that's it */
            if ( finfo.type_aux == FB_AUX_VGA_PLANES_VGA4 ) {
                if ( ioperm(0x3b4, 0x3df - 0x3b4 + 1, 1) < 0 ) {
                    GAL_SetError("NEWGAL>FBCON: No I/O port permissions\n");
                    FB_VideoQuit(this);
                    return(-1);
                }
                this->SetVideoMode = FB_SetVGA16Mode;
                break;
            }
            /* Fall through to unsupported case */
#endif /* VGA16_FBCON_SUPPORT */
        default:
            GAL_SetError("NEWGAL>FBCON: Unsupported console hardware\n");
            FB_VideoQuit(this);
            return(-1);
    }
    switch (finfo.visual) {
        case FB_VISUAL_TRUECOLOR:
        case FB_VISUAL_PSEUDOCOLOR:
        case FB_VISUAL_STATIC_PSEUDOCOLOR:
        case FB_VISUAL_DIRECTCOLOR:
            break;
        default:
            GAL_SetError("NEWGAL>FBCON: Unsupported console hardware\n");
            FB_VideoQuit(this);
            return(-1);
    }

#if 0
    /* Check if the user wants to disable hardware acceleration */
    { const char *fb_accel;
        fb_accel = getenv("GAL_FBACCEL");
        if ( fb_accel ) {
            finfo.accel = atoi(fb_accel);
        }
    }
#endif

    /* Memory map the device, compensating for buggy PPC mmap() */
    mapped_offset = (((long)finfo.smem_start) -
                    (((long)finfo.smem_start)&~(getpagesize () - 1)));
    mapped_memlen = finfo.smem_len+mapped_offset;
   
#ifdef __uClinux__
#  ifdef __TARGET_BLACKFIN__
    mapped_mem = mmap(NULL, mapped_memlen,
                            PROT_READ|PROT_WRITE, MAP_PRIVATE, console_fd, 0);
#  else
    mapped_mem = mmap(NULL, mapped_memlen,
                            PROT_READ|PROT_WRITE, 0, console_fd, 0);
#  endif
#else
    // 执行内存映射[luther.gliethttp]
    // #define mapped_mem        (this->hidden->mapped_mem)
    mapped_mem = mmap(NULL, mapped_memlen,
                            PROT_READ|PROT_WRITE, MAP_SHARED, console_fd, 0);
#endif
   
    if (mapped_mem == (char *)-1) {
        GAL_SetError("NEWGAL>FBCON: Unable to memory map the video hardware\n");
        mapped_mem = NULL;
        FB_VideoQuit(this);
        return(-1);
    }

    /* Determine the current screen depth */
    if ( ioctl(console_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ) {
        GAL_SetError("NEWGAL>FBCON: Couldn't get console pixel format\n");
        FB_VideoQuit(this);
        return(-1);
    }
    vformat->BitsPerPixel = vinfo.bits_per_pixel; // 1个像素对应bits位数[luther.gliethttp]
    if ( vformat->BitsPerPixel < 8 ) {
        /* Assuming VGA16, we handle this via a shadow framebuffer */
        vformat->BitsPerPixel = 8; // 强制为1个字节[luther.gliethttp]
    }
    for ( i=0; i<vinfo.red.length; ++i ) {
        // 生成红色mask,以vinfo.red.offset位开始向高位数vinfo.red.length个位,
        // 比如vinfo.red.offset等于0,
        // vinfo.red.length等于5,
        // 那么该for循环完毕之后
        // vformat->Rmask = 0x1F;[luther.gliethttp]
        vformat->Rmask <<= 1;
        vformat->Rmask |= (0x00000001<<vinfo.red.offset);
    }
    for ( i=0; i<vinfo.green.length; ++i ) {
        vformat->Gmask <<= 1;
        vformat->Gmask |= (0x00000001<<vinfo.green.offset);
    }
    for ( i=0; i<vinfo.blue.length; ++i ) {
        vformat->Bmask <<= 1;
        vformat->Bmask |= (0x00000001<<vinfo.blue.offset);
    }
    for ( i=0; i<vinfo.transp.length; ++i ) {
        // 透明色[luther.gliethttp]
        vformat->Amask <<= 1;
        vformat->Amask |= (0x00000001<<vinfo.transp.offset);
    }
    saved_vinfo = vinfo;

    /* Save hardware palette, if needed */
    FB_SavePalette(this, &finfo, &vinfo); // 保存调色板

    /* If the I/O registers are available, memory map them so we
       can take advantage of any supported hardware acceleration.
     */
    if ( finfo.accel && finfo.mmio_len ) {
        // 如果存在寄存器io映射,那么mmap它们,这样有些硬件特性特效,我们可以去强硬控制[luther.gliethttp]
        mapped_iolen = finfo.mmio_len;
        mapped_io = mmap(NULL, mapped_iolen, PROT_READ|PROT_WRITE,
                         MAP_SHARED, console_fd, mapped_memlen);
        if ( mapped_io == (char *)-1 ) {
            /* Hmm, failed to memory map I/O registers */
            mapped_io = NULL;
        }
    }

#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
    if (mgIsServer) {
#endif
        /* Query for the list of available video modes */
        current_w = vinfo.xres; // x轴像素数,对于vga=0x0317,该值为1024
        current_h = vinfo.yres; // y轴像素数,对于vga=0x0317,该值为768 [luther.gliethtttp]
        current_index = ((vinfo.bits_per_pixel+7)/8)-1; // 一个像素字节数-1.
#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
    }
#endif

    /* Fill in our hardware acceleration capabilities */
    this->info.hw_available = 1;
    this->info.video_mem = finfo.smem_len/1024;
    if ( mapped_io ) {
        switch (finfo.accel) {
            case FB_ACCEL_MATROX_MGA2064W:
            case FB_ACCEL_MATROX_MGA1064SG:
            case FB_ACCEL_MATROX_MGA2164W:
            case FB_ACCEL_MATROX_MGA2164W_AGP:
            case FB_ACCEL_MATROX_MGAG100:
            /*case FB_ACCEL_MATROX_MGAG200: G200 acceleration broken! */
            case FB_ACCEL_MATROX_MGAG400:
#ifdef FBACCEL_DEBUG
            fprintf(stderr, "NEWGAL>FBCON: Matrox hardware accelerator!\n");
#endif
            FB_MatroxAccel(this, finfo.accel);
            break;
            case FB_ACCEL_3DFX_BANSHEE:
#ifdef FBACCEL_DEBUG
            fprintf(stderr, "NEWGAL>FBCON: 3DFX hardware accelerator!\n");
#endif
            FB_3DfxAccel (this, finfo.accel);
            break;
#ifdef FB_ACCEL_NEOMAGIC_NM2070
            case FB_ACCEL_NEOMAGIC_NM2200:
            case FB_ACCEL_NEOMAGIC_NM2230:
            case FB_ACCEL_NEOMAGIC_NM2360:
            case FB_ACCEL_NEOMAGIC_NM2380:
#ifdef FBACCEL_DEBUG
            fprintf(stderr, "NEWGAL>FBCON: NeoMagic hardware accelerator!\n");
#endif
            FB_NeoMagicAccel (this, finfo.accel);
#endif
            break;
            default:
#ifdef FBACCEL_DEBUG
            fprintf(stderr, "NEWGAL>FBCON: Unknown hardware accelerator!\n");
#endif
            break;
        }
    }

    if (FB_OpenKeyboard(this) < 0) { // 我们这里函数FB_OpenKeyboard直接返回
        FB_VideoQuit(this);
        return(-1);
    }

    /* We're done! */
    return(0);
}

GAL_Surface * GAL_CreateRGBSurface (Uint32 flags,
            int width, int height, int depth,
            Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)
{
    GAL_VideoDevice *video = current_video;
    GAL_VideoDevice *this  = current_video;
    GAL_Surface *screen;
    GAL_Surface *surface;

    /* Check to see if we desire the surface in video memory */
    if ( video ) {
    // #define GAL_PublicSurface    (current_video->screen)
        screen = GAL_PublicSurface;
    } else {
        screen = NULL;
    }
    if ( screen && ((screen->flags&GAL_HWSURFACE) == GAL_HWSURFACE) ) {
        // 表示为硬件framebuffer对应的screen缓冲区
        // 我们的flags = GAL_SWSURFACE; 软接口
        if ( (flags&(GAL_SRCCOLORKEY|GAL_SRCALPHA)) != 0 ) {
            flags |= GAL_HWSURFACE;
        }
        if ( (flags & GAL_SRCCOLORKEY) == GAL_SRCCOLORKEY ) {
            if ( ! current_video->info.blit_hw_CC ) {
                flags &= ~GAL_HWSURFACE;
            }
        }
        if ( (flags & GAL_SRCALPHA) == GAL_SRCALPHA ) {
            if ( ! current_video->info.blit_hw_A ) {
                flags &= ~GAL_HWSURFACE;
            }
        }
    } else {
        flags &= ~GAL_HWSURFACE; // 不能使用硬件缓冲区
    }

    /* Allocate the surface */
    surface = (GAL_Surface *)malloc(sizeof(*surface)); // 申请内存
    if ( surface == NULL ) {
        GAL_OutOfMemory();
        return(NULL);
    }
    if ((flags & GAL_HWSURFACE) == GAL_HWSURFACE)
        surface->video = current_video; // 挂上它对应的物理video
    else
        surface->video = NULL; // GAL_SWSURFACE; 软接口,所以没有物理video

    surface->flags = GAL_SWSURFACE; // 为软接口,并且它的video为NULL.

    if ( (flags & GAL_HWSURFACE) == GAL_HWSURFACE ) {
        depth = screen->format->BitsPerPixel;
        Rmask = screen->format->Rmask;
        Gmask = screen->format->Gmask;
        Bmask = screen->format->Bmask;
        Amask = screen->format->Amask;
    }
    // depth为一个像素对应字节数
    // RGBA分别对应红绿蓝和透明色对应的mask掩码值[luther.gliethttp]
    surface->format = GAL_AllocFormat(depth, Rmask, Gmask, Bmask, Amask);
    if ( surface->format == NULL ) {
        free(surface);
        return(NULL);
    }
    if ( Amask ) {
        surface->flags |= GAL_SRCALPHA; // 含有透明色
    }
    surface->w = width;
    surface->h = height;
    surface->pitch = GAL_CalculatePitch(surface);
    surface->pixels = NULL;
    surface->offset = 0;
    surface->hwdata = NULL;
    surface->map = NULL;
    surface->format_version = 0;
    GAL_SetClipRect(surface, NULL);

    /* Get the pixels */
    if ( ((flags&GAL_HWSURFACE) == GAL_SWSURFACE) ||
                (video->AllocHWSurface(this, surface) < 0) ) {
        if ( surface->w && surface->h ) { // 我们这里w和h都定义为0[luther.gliethttp]
            // pitch为屏幕一行像素所占字节数
            // surface->h为一个屏幕一共多少行
            // 所以它们2个相乘之后,就是整个屏幕所需内存大小[luther.gliethttp]
            surface->pixels = malloc(surface->h*surface->pitch); // 申请屏幕所有像素对应的字节内存[luther.gliethttp]
            if ( surface->pixels == NULL ) {
                GAL_FreeSurface(surface);
                GAL_OutOfMemory();
                return(NULL);
            }
            /* This is important for bitmaps */
            memset(surface->pixels, 0, surface->h*surface->pitch); // 清空内存数据
            surface->flags &= ~GAL_HWSURFACE; // 软内存空间标志
        }
    }

    /* Allocate an empty mapping */
    surface->map = GAL_AllocBlitMap();
    if ( surface->map == NULL ) {
        GAL_FreeSurface(surface);
        return(NULL);
    }

    /* The surface is ready to go */
    surface->refcount = 1; // ok了
#ifdef CHECK_LEAKS
    ++surfaces_allocated;
#endif
    return(surface);

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
framebuffer驱动全篇
关于lwip中pbuf
添加lib库自动搜索路径到/etc/ld.so.conf,然后ldconfig使设置生效
linux下查看用户组的信息
浅析eCos系统Redboot单元启动流程
如何在init.rc中添加/dev/字符设备节点创建权限
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服