[自制操作系统] BMP格式文件读取&图形界面系统框架-应用接口设计

【操作系统】30天自制操作系统--(22)图形处理API 本章主要编写了内存分配API、几个图形处理的API(包括画点、画线、窗口刷新以及窗口关闭)以及键盘输入的API。 一 编写malloc API 应用程序需要多少内存应该在编写应用程序时指出来比较好,这就需要应用程序调用memman、malloc、free等API。 【1】设计几个API如下: 【2】依据此修改 console.c : int *hrb_api(int eid, int esi, int ebp, int esp. int ebx, int ... 阅读详情

Github : https://github.com/He11oLiu/JOS

本文将介绍在本人JOS中实现的简单图形界面应用程序接口,应用程序启动器,以及一些利用了图形界面的示例应用程序。

本文主要涉及以下部分:

  • 内核/用户RW/RW调色板framebuffer共享区域
  • 8bit颜色深度BMP格式图片读取与绘制
  • 读BMP头总是出现问题?不合理的数据?
  • 为啥读出来的图片颜色怪怪的!!
  • 为啥是倒的,还有的运气不好出错了
  • 如果是想绘制多个图片在一页上,调色板问题??
  • 如果读到一个32位色的图片咋办?
  • 图形化界面数据结构,框架以及接口设计
  • 利用图形化接口实现应用程序:
  • 日历程序(实时时钟刷新)
  • 系统信息获取
  • 终端CGA模拟器

PART1

framebuffer

在图形库中,已经将图形模式打开,将显存映射到内存中的一段空间。并进行了简单的测试。

实际上,直接对显存写是很不负责任的行为。很早之前在写java的界面的时候,就接触了双缓冲技术,其实与显示有关的思想都是差不多的,我们应该提供一个framebuffer。当完成一个frame后,再将这个frame update到显存中。

uint8_t *framebuffer;
void init_framebuffer(){
    if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
        panic("Not enough memory for framebuffer!");
}

void update_screen(){
    memcpy(graph.vram,framebuffer,graph.scrnx*graph.scrny);
}

经过实现kmalloc与kfree,已经可以分配这个缓冲区,并直接向缓冲区写入,最后再进行update

#define PIXEL(x, y) *(framebuffer + x + (y * graph.scrnx))
int draw_xx()
{
    xxx;
    update_screen();
}

canvas (这种思路已经废弃)

从一个单一的应用程序角度来看,应分配一个单独的画布,然后选择在一个位置显示。

typedef struct canvas
{
    uint16_t width;
    uint16_t height;
    uint8_t *data;
} canvas_t;

设计的模式是,与文件系统服务器类似,提供一个图形系统服务器,用于接收从其他的程序发来的请求。请求包括显示的位置,以及canvas。该服务器将canvas写入frambuffer并update。其他程序与图形服务器通过IPC进行通讯。

剩余的事情就可以交给用户空间了。包括对canvas的处理,更新显示,添加各种元件。之前写的字库也可以不用写在内核了...

首先实现绘制canvas。

int draw_canvas(uint16_t x, uint16_t y, canvas_t *canvas)
{
    int i, j;
    int width = (x + canvas->width) > graph.scrnx ? graph.scrnx : (x + canvas->width);
    int height = (y + canvas->height) > graph.scrny ? graph.scrny : (y + canvas->height);
    cprintf("width %d height %d\n",width,height);
    for (j = y; j < height; j++)
        for (i = x; i < width; i++)
            PIXEL(i, j) = *(canvas->data + (i - x) + (j - y) * canvas->width);
    update_screen();
    return 0;
}

然后在lib中新建canvas的相关方法:

int canvas_init(uint16_t width, uint16_t height, canvas_t *canvas);
int canvas_draw_bg(uint8_t color, canvas_t *canvas);
int canvas_draw_ascii(uint16_t x, uint16_t y, char *str, uint8_t color, canvas_t *canvas);
int canvas_draw_cn(uint16_t x, uint16_t y, char *str, uint8_t color, canvas_t *canvas);
int canvas_draw_rect(uint16_t x, uint16_t y, uint16_t l, uint16_t w, uint8_t color, canvas_t *canvas);

其中只需要将原来的PIXAL宏换为

#define CANVAS_PIXEL(canvas, x, y) *(canvas->data + x + (y * canvas->width))

测试canvas

    canvas_t canvas_test;
    canvas_init(300, 200, &canvas_test);
    uint8_t testcanvas[60000];
    canvas_test.data = (uint8_t *)testcanvas;
    canvas_draw_bg(0x22,&canvas_test);
    canvas_draw_ascii((uint16_t)2, (uint16_t)2, test_ascii, (uint8_t)0xff, &canvas_test);
    canvas_draw_cn((uint16_t)2, (uint16_t)50, test_cn, (uint8_t)0xff, &canvas_test);
    draw_canvas(500, 500, &canvas_test);

图像处理的两种设计与遇到的问题

  • 第一种设计与之前描述的一致:

提供一个图像服务器,接收请求,从用户进程传来需要画的画布和显示位置,并在位置上进行绘画。这种方式遇到的问题是画布过大,一页可能装不下。需要mmap(还没写)

  • 第二种设计是一个launcher和application两个单独的单页面切换制度。

这样就是launcher提供应用启动界面,application提供应用界面。

重新回顾了一下内存分配,内核与用户态数据共享的方法后,决定先就第二个思路实现一个简单的用户内核均可见可读写的Framebuffer。

实现RW/RW的Framebuffer

分析如何做才能内核用户均可读写

首先分析一个之前做过的pages,是如何做到用户态可以读,内核态可以写的。

  • 在mem_init的时候在在内核空间中分配指定的空间给pages

c pages = boot_alloc(sizeof(struct PageInfo) * npages); memset(pages, 0, sizeof(struct PageInfo) * npages);

  • 利用boot_map_region将其映射到内核页表中的UPAGES的位置。

c boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U | PTE_P);

  • 这样内核中依然可以通过pages访问页表,而用户程序在entry的时候通过给pages变量赋予存储位置

.globl pages .set pages, UPAGES

也可以通过pages变量进行访问。

预留内存用于framebuffer

再思考如果需要这么一个framebuffer,我们需要放到哪里。仿造上面的UVPD,UPAGES,等,决定就放在接近ULIM的位置。一个PTSIZE也远超我们需要的空间,为以后扩展也留下了余量。

/*
 * ULIM, MMIOBASE -->  +------------------------------+ 0xef800000
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *  FRAMEBUF    ---->  +------------------------------+ 0xef000000
 *                     |        FRAME BUFFER          | RW/RW  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xeec00000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xee800000
 */

// User read-only virtual page table (see 'uvpt' below)
#define UVPT (ULIM - PTSIZE)
// Read-only copies of the Page structures
#define UPAGES (UVPT - PTSIZE)
// Read-write framebuffer
#define FRAMEBUF (UPAGES - PTSIZE)
// Read-only copies of the global env structures
#define UENVS (FRAMEBUF - PTSIZE)
// #define UENVS (UPAGES - PTSIZE)
什么时候映射到内核的页表?

由于图像初始化在内存初始化之后,需要留一个接口来进行映射。(boot_map是隐式函数)

void map_framebuffer(void *kva)
{
    boot_map_region(kern_pgdir, FRAMEBUF, PTSIZE, PADDR(kva), PTE_W | PTE_U | PTE_P);
}

在分配好内核中的Framebuffer就可以开始映射了

void init_framebuffer()
{
    if ((framebuffer = (uint8_t *)kmalloc((size_t)(graph.scrnx * graph.scrny))) == NULL)
        panic("Not enough memory for framebuffer!");
    map_framebuffer(framebuffer);
}
用户程序如何访问?

在libmain的时候初始化即可

    framebuffer = (uint8_t *)FRAMEBUF;
用户态刷新屏幕?

用户程序在写完frambuffer后,如何才能刷新屏幕?这又需要一个新的内核调用

static int sys_updatescreen()
{
    update_screen();
    return 0;
}

配套的一些代码就不解释了。

PART2

上一个部分已经将一个用户与内核均可读写的缓冲区域,并提供了一个系统调用,用于将显示缓存内容拷贝至MMIO显存。从理论上来说,用户空间的程序现在已经可以直接在这块Framebuffer上绘制任何图形。

但是对于一个友好的用户界面,至少要支持一种格式的图片显示。这里选择一种最简单的,没有压缩过的位图显示实现。推荐各位想自己写图形界面的小伙伴也从这里入手。

关于BMP的读取可以参考这篇文章256-Color VGA Programming in C Bitmaps & Palette Manipulation。要注意详细读其中的每一个细节,直接扫一眼看代码写的话会遇到很多问题,下面会提到我遇到的问题与解决方案。

Bitmap图片显示

There are many file formats for storing bitmaps, such as RLE, JPEG, TIFF, TGA, PCX, BMP, PNG, PCD and GIF. The bitmaps studied in this section will be 256-color bitmaps, where eight bits represents one pixel.

One of the easiest 256-color bitmap file format is Windows' BMP. This file format can be stored uncompressed, so reading BMP files is fairly simple.

Windows' BMP是没有压缩过的,所以读这种BMP会非常方便。这里也准备就支持这种格式的图片。

There are a few different sub-types of the BMP file format. The one studied here is Windows' RGB-encoded BMP format. For 256-color bitmaps, it has a 54-byte header (Table III) followed by a 1024-byte palette table. After that is the actual bitmap, which starts at the lower-left hand corner.

BMP的文件格式如下:

DataDescription
WORD Type;File type. Set to "BM".
DWORD Size;Size in BYTES of the file.
DWORD Reserved;Reserved. Set to zero.
DWORD Offset;Offset to the data.
DWORD headerSize;Size of rest of header. Set to 40.
DWORD Width;Width of bitmap in pixels.
DWORD Height;Height of bitmap in pixels.
WORD Planes;Number of Planes. Set to 1.
WORD BitsPerPixel;Number of bits per pixel.
DWORD Compression;Compression. Usually set to 0.
DWORD SizeImage;Size in bytes of the bitmap.
DWORD XPixelsPerMeter;Horizontal pixels per meter.
DWORD YPixelsPerMeter;Vertical pixels per meter.
DWORD ColorsUsed;Number of colors used.
DWORD ColorsImportant;Number of "important" colors.

下面就我遇到的四个严重的问题,来实现BMP格式的图片读取。

Q1:读头总是出现问题?不合理的数据?

这里要注意GCC默认4字节对齐!!!!!!

但是Bitmap的文件头是14Bytes,如果不加特殊标记,其会变成16Bytes,导致文件偏移错误

typedef struct bitmap_fileheader
{
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
}__attribute__((packed)) bitmap_fileheader;

typedef struct bitmap_infoheader
{
    uint32_t biSize;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    uint32_t biXPelsPerMeter;
    uint32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
} bitmap_infoheader;

这里添加的__attribute__((packed))关键字用于告诉编译器,最小单位进行对齐,而不使用默认的四单位进行对齐。

Q2:为啥读出来的图片颜色怪怪的!!

最开始设置VBE的时候,我以为所谓8位色就是真8位色,之前徒手撸FPGA的显卡的时候也是这么设计的,直接读取后分位后丢给一个D/A输出给VGA变成各自的颜色信号。但是实际系统没有这么简单,其实现了一个8位到32位的对应关系,提供了256位色的调色板。这样能支持更自由的调色方案,显示更加定制化的颜色。所以之前我没有初始化调色板,利用了系统默认的调色板,所以显示才出现问题。

但是理解BMP又出现了偏差,以为大体上是遵循RGB3bit3bit2bit的配色方案,先写了一个初始化调色板的函数:

void init_palette()
{
    int i;
    outb(0x03c8, 0);
    for (i = 0; i < 256; i++)
    {
        outb(0x03c9, (i & 0xe0) >> 2); //| 0xA);
        outb(0x03c9, (i & 0x1c) << 1); //| 0xA);
        outb(0x03c9, (i & 0x03) << 3); //| 0xA);
    }
}

其选择了最接近想表达的颜色的32位颜色并给端口输出。但是颜色还是不大对劲,调色板应该不是这么简单的对应关系。

重新读之前文章的介绍,发现每一个图片文件都有自己的调色板,这种调色板还不太一样,之后使用PS绘制系统图标的时候深有感触,后面再说。

现在面临的主要问题是,我们需要从用户空间读取文件后,才能取出调色板的具体内容,但是通过端口与VGA调色板的通讯在我的设计里面是不能够通过用户空间实现的。那么又要进入内核。那么这个调色板的信息如何传给内核?动态分配的话不能通过栈来传,内核没有用户的页表,也就无法通过地址进行访问。

为了能够从用户空间读取调色板配置文件,并在内核中修改调色板,在原来设计framebuffer的地址上又重新设计了一块专门用于保存调色板的区域,与之前的framebuffer一样,都是RW/RW的。

计算一下占用的空间:256 * sizeof(uint8_t) + sizeof(uint8_t)*SCRNSIZE还是比PTSIZE小,没关系,继续用之前分配的memorylayout,只需要定义一个结构体方便我们来算偏移即可。

所以对于一个BMP图片浏览器,显示图片的整个流程是这样的:

  • 用户读BMP文件头
  • 用户读BMP调色板
  • 放入与内核共享的调色板空间
  • 系统调用内核修改调色板
  • 用户读文件内容
  • 用户写入与内核共享的显示缓存空间
  • 系统调用更新屏幕

到这里还有误解,认为BMP的调色板可能大致一致 "然后发现几个文件的调色基本一致,于是单独设计了一个用于保存调色板信息的文件,用以下工具导出"。当时的记录是这样,naive!但是这个程序对于其后导出PS调色板有帮助,所以也放在这里。

void read_bmp_palette(char *file)
{
    FILE *fp;
    long index;
    int x;
    /* open the file */
    if ((fp = fopen(file, "rb")) == NULL)
    {
        printf("Error opening file %s.\n", file);
        exit(1);
    }
    uint8_t buf[1000];
    bitmap_fileheader head;
    bitmap_infoheader info;
    uint16_t width, height;
    bitmap_image image;
    bitmap_infoheader *infoheader = &info;
    fread(&head, sizeof(bitmap_fileheader),1,fp);
    fread( &info, sizeof(bitmap_infoheader),1,fp);
    struct palette palette[256];
    FILE *fout = fopen("palette.plt", "wb");
    
    for (int i = 0; i < 256; i++)
    {
        fread(&palette[i], sizeof(struct palette), 1, fp);
        palette[i].rgb_red >>= 2;
        palette[i].rgb_green >>= 2;
        palette[i].rgb_blue >>= 2;
        fwrite(&palette[i], sizeof(struct palette), 1, fout);
    }
    fclose(fout);
    fclose(fp);
}

好了,到这里运气好的话,应该可以正常颜色绘制出来一个位图了。(那啥读取位图内容显示在屏幕上的代码实在太简单了,就不单独说了)

Q3:为啥是倒的,还有的运气不好出错了

之前之所以说运气好,是因为刚好这个图片信息中的高为正的,那么按照基本逻辑,可以画出来一个倒的图片。还是太naive,不好好看文档中的头文件具体参数描述,想当然的给了图片高为一个无符号数。

在BMP的文件头中,高为一个有符号数。正表示下面的位图像素信息是倒着来的,负表示下面的位图像素信息是正着的……这个设计,好吧...

Q4:如果是想绘制多个图片在一页上,调色板问题??

在Q2中提到,想用一个调色板文件预配置后就不管其他图片的调色板的思路太单纯了...当使用一些比较fancy的素材进来的时候,发现其颜色根本完全不一样,失真的可怕。

为了更加理解调色板这个设定,我们需要一个photoshop。设置图片为图像->模式->索引模式后,就可以生成我们需要的位图了。注意这里的设置页面:

可以发现系统有自己的调色板,可能用于绘制所有的图标使用的。(当然可能也已经是历史的产物了)后面我将用相同的思路实现图标的绘制。还有一些局部的选项,这样就会利用一个颜色更加单一,但是转化出来的图片更接近32位色的图片的调色板来生产了。

打开图像->模式->颜色表可以看到当前图片使用的调色板:

可以看到它完全不按照套路出牌,并没有之前说的R3G3B2的影子。

所以对于一个页面,如何选择调色板?我的方案是把这个页面所有的素材丢到一个ps文件中,并生成针对这个页面还原度最高的调色板方案。在绘制这个页面的时候先载入这个页面的调色板,再进行绘制。

PS可以导出调色板,按照官方的文档,也是一个简单的二进制的堆叠,与上面的思路类似写一个调色板转系统plt文件的导出即可。

Q5:如果读到一个32位色的图片咋办?

好吧,我的选择是不读,可以在网上找找32位色妥协到8位色的算法,然而实在效果非常糟糕,单独生成调色板算法就复杂了,不如交给PS。毕竟这不是操作系统的重点。

Part3

本部分将解释我设计的图形化界面数据结构,框架以及接口。

其实这部分设计的比较乱,也就只能支持单页面切换的需求了。作为一个技术试探是足够了,但是扩展性很差,想继续在这上面做文章可能需要推倒重来。

先看效果图:

界面由标题和内容组成,界面是应用程序请求屏幕资源的基本单位。界面的数据结构如下:

struct interface
{
    uint8_t titletype;
    char title[MAX_TITLE];
    uint8_t title_textcolor;
    uint8_t title_color;
    uint8_t content_type;
    uint8_t content_color;

    // about the size and buff of interface
    uint16_t scrnx;
    uint16_t scrny;
    uint8_t *framebuffer;
};

其包含了这个界面的基本信息,以及当前屏幕的各项参数,各种函数将直接向framebuffer上操作。

void draw_interface(struct interface *interface);
void draw_title(struct interface *interface);
// if color == back means transparent
int draw_cn(uint16_t x, uint16_t y, char *str, uint8_t color, uint8_t back, uint8_t fontmag, struct interface *interface);
int draw_ascii(uint16_t x, uint16_t y, char *str, uint8_t color, uint8_t back, uint8_t fontmag, struct interface *interface);
void draw_fontpixel(uint16_t x, uint16_t y, uint8_t color, uint8_t fontmag, struct interface *interface);
void interface_init(uint16_t scrnx, uint16_t scrny, uint8_t *framebuffer, struct interface *interface);
void add_title(char *title, uint8_t title_textcolor, uint8_t title_color, struct interface *interface);
int init_palette(char *plt_filename, struct frame_info *frame);
void draw_content(struct interface *interface);
int draw_screen(uint16_t x, uint16_t y, struct screen *screen, uint8_t color, uint8_t back, uint8_t fontmag);

提供了以上基本操作,实现都很简单,没有做错误处理。

值得一提的是字体的设置。由于用的点阵字库,放大后会马赛克。这里使用的方法为打包具体绘制像素方法至draw_fontpixel,其提供了多个像素抽象为一个字体像素进行统一绘制的方法。

Part 4

本部分终于到了图形界面的程序应用了。具体应用如何使用上面设计的接口呢?

首先看一个最简单的例子:

#include <inc/lib.h>
#define BACKGROUND 0x00
struct interface interface;

void input_handler();
void display_info();

void umain(int argc, char **argv)
{
    int r;
    // 初始化本界面使用的调色板
    if ((r = init_palette("/bin/sysinfo.plt", frame)) < 0)
        printf("Open palette fail %e\n", r);
    // 初始化界面信息
    interface_init(graph.scrnx, graph.scrny, graph.framebuffer, &interface);
    interface.titletype = TITLE_TYPE_TXT;
    strcpy(interface.title, "System information");
    interface.title_color = 0x5a;
    interface.title_textcolor = 0xff;
    interface.content_type = APP_NEEDBG;
    interface.content_color = BACKGROUND;
    // 绘制界面
    draw_interface(&interface);
    // 绘制Bitmap
    if ((r = draw_bitmap("/bin/sysinfo.bmp", 100, 160, &interface)) < 0)
        printf("Open clock back fail %e\n", r);
    // 显示信息
    display_info();
    // 绘制结束,刷新屏幕
    sys_updatescreen();
    // 处理按键中断
    input_handler();
}

void input_handler()
{
    unsigned char ch;
    ch = getchar();
    while (1)
    {
        switch (ch)
        {
        case KEY_ESC:
            exit();
        }
        ch = getchar();
    }
}

void display_info()
{
    ...
    struct sysinfo info;
    // 通过系统调用获取一些系统信息
    sys_getinfo(&info);
    draw_ascii(display_x, display_y, "Sys    : He11o_Liu's JOS version 0.1", 0xff, 0x00, fontmeg, &interface);

    display_y += font_height;
    draw_ascii(display_x, display_y, "Github : https://github.com/He11oLiu/JOS", 0xff, 0x00, fontmeg, &interface);
    display_y += font_height;
    draw_ascii(display_x, display_y, "Blog   : http://blog.youkuaiyun.com/he11o_liu", 0xff, 0x00, fontmeg, &interface);
    ...
}

一个简单的具有图像界面的程序由以下步骤:

  • 初始化本界面使用的调色板
  • 初始化界面信息
  • 绘制界面,绘制内容
  • 更新屏幕
  • 开启按键处理,具体事件具体处理

关于具体应用程序的一些要点

应用启动器

启动器算比较复杂的一个部分,专门设计了一个单独的数据结构和绘制方法:

struct launcher_content
{
    int app_num;
    int app_sel;
    uint8_t background;
    char icon[MAX_APP][MAX_PATH];
    char app_bin[MAX_APP][MAX_PATH];
};

void draw_launcher(struct interface *interface, struct launcher_content *launcher);

用icon来保存对应的app的图标文件路径,用app_bin来保存对应的程序的路径。当选择了对应的程序的时候spawn这个程序,并等待其运行结束后回收进程并重绘启动器:

void launch_app()
{
    char *app_bin = launcher.app_bin[launcher.app_sel];
    int r;
    char *argv[2];
    argv[0] = app_bin;
    argv[1] = 0;
    printf("[launcher] Launching %s\n",app_bin);
    if ((r = spawn(app_bin, (const char **)argv)) < 0)
    {
        printf("App %s not found!\n",app_bin);
        return;
    }
    wait(r);
    printf("[launcher] %s normally exit\n",app_bin);
    init_palette("/bin/palette.plt", frame);
    refresh_interface();
}
日历

由于没有写系统时钟,只提供了对于RTC的系统调用。这里实现Fork了一个进程用于监控RTC的更新,并在适当时候更新屏幕,主进程用于监听键盘,并在退出的时候摧毁子进程。

系统信息

这个程序的代码已经放在上面了,主要是设计了一个新的syscall,用于从内核中返回一些基本系统信息。

终端[Working on it]

已经实现了一个CGA模拟器,并提供单独的fd device

Part 收获

这个部分将记录这几天写图形界面的收获。

之前看知乎上的大佬们的论调:图形界面和操作系统有啥关系?没啥好写的,简单!其实写写简单的图形界面一个是转换一下思路,有简单可见的产出激励,另一个是进一步理解体会操作系统的设计,并实际修改一些JOS中的设计,并实现一些类似之前照着任务指南写出来的功能。In another words, get your hands dirty.

用户与内核的关系

按照JOS的思路,还是希望保持一个相对小的内核,提供最基本的服务,剩下的交给用户空间玩耍。但是到实际的问题上,包括了

  • 功能的划分,用户需要的服务能否在用户态实现大部分,内核实现小部分(如上次的用户空间的页错误处理与这次的Framebuffer与屏幕更新)。这样的设计更加flexable,并且保证了呆在内核中的时间非常短暂(毕竟还用着big kernel lock…)

  • 内核与用户空间的信息交换。栈或者固定地址的交互。

栈比较灵活,可以在每次的系统调用的时候直接压进去,交换完了后再取回来。但是只能传值,传的内容比较少。

固定地址则使得系统变得不那么灵活,不利于扩展与移植。但是可以高效的大量数据交换。在写这块的时候实现了kmalloc,并理解了之前mem_init时做的各种映射的意义。

用户与用户的关系(用户空间程序)

理想的用户与用户的关系是平齐的(不提供服务的用户),在写用户程序的时候不知道其父进程是谁,也不会要求子进程知道自己的存在。但是跨进程之间的服务需求仍然存在,如对于一个进程输出的获取,或图形界面中的界面重叠。这就需要一个服务提供者的存在,来抽象用户之间的需求。比如之前设计的文件系统服务器,比如这回本来准备实现的图形界面服务器。

文件描述符 | 句柄 | FD

这部分感触最深的是unix中的文件描述符的设计,简直太妙。将所有的内容全部抽象成文件,就可以灵活的在不同的需求之前切换。最简单的读写文件,读写串口,读写屏幕,pipe均使用的这种抽象。

这种抽象将抽象层和具体实现层分开,降低耦合度的同时提供了非常高的灵活性。

GD32F4xx USART应用_DGUS屏控制 1、DGUS屏简介 DGUS(DWIN Graphic Utilized Software)是北京迪文科技有限公司基于K600+内核迪文屏设计的智慧型、图形界面、人机系统软件。 1.1 DGUS屏的主要特点 把GUI分解成控件,按照页面来进行配置,控件显示直接由变量控制;在通过PC软件(DGUS Tool)配置好控件文件下载到DGUS屏后,用户只需要通过串口改写变量值即可实现控件显示的响应改变。 触控屏或键盘录入过程,通过PC软件按照页面定义的触控文件来控制,用户软件只需要定时或者设置参数改变时的串 阅读详情

相关推荐

掌握绘图软件核心功能:放大缩小、保存与截屏

在当今数字化时代,一个具有基本绘图功能的软件几乎是每一个数字设备的标配。本章节将从整体上介绍一个简单绘图软件的典型功能。它不仅包括创建、编辑图像等基础操作,还涵盖了图像的放大缩小、保存特定格式文件以及截屏等功能。简单绘图软件通常提供以下基础功能:创建和编辑图像:用户可以使用不同的工具,如画笔、铅笔、橡皮擦等,进行基本的绘图操作。图像变换:通过图像放大缩小功能,用户可以调整图像的尺寸以适应不同的显示需求。文件管理。

weixin_42551310的博客 1161

[自制操作系统] BMP格式文件读取&图形界面系统框架/应用接口设计

本文将介绍在本人JOS中实现的简单图形界面应用程序接口,应用程序启动器,以及一些利用了图形界面的示例应用程序。

He11o_Liu的专栏 3638

从EXE/DLL文件中提取ICO图标的实用工具设计与实现

提到图标,很多人第一反应是PNG或SVG。但在Windows世界里,.ico才是真正的原生王者。它可不是普通的图像格式,而是一种多图像容器格式,专为操作系统图标系统量身打造。它的核心由三部分构成:ICONDIR:全局头信息:多个图标的描述数组多个DIB位图数据块:实际像素内容// 必须为0// 类型:1表示图标,2表示光标// 图标数量} ICONDIR;每一个都记录了一个图标的宽度、高度、色深以及在文件中的偏移位置。最关键的是,一个.ico。

weixin_42300144的博客 899

java 画图 怎么清除已经画出来的图形_计算机自制操作系统(九):漂亮的桌面是怎么来的...

如果计算机一直都是DOS系统那样的黑底白字界面,就早已失去了它的魅力。我们制作的操作系统目前看起来很不友好,之前在“(五):引导和内核程序组织方式”一章中,我做了一个如下的“图形化”界面:但严格意义上讲,这不能算得上是一个图形化的界面,图形明显看上去比较模糊和粗糙。因为它是利用的BIOS的INT 10H中的"开窗填充"功能,在“文本模式”下把80*25的相关字符位设置了特殊颜色而已:我们制作的操作...

weixin_39898011的博客 279

基于Qt框架的本地视频播放器开发全解析:从源码解压到功能扩展

在跨平台桌面应用开发中,Qt框架因其强大的GUI能力和丰富的模块库而备受青睐。其核心原理在于通过信号槽机制实现对象间通信,并结合元对象系统提供运行时类型信息。Qt Multimedia模块作为处理音视频内容的技术基石,为开发者提供了QMediaPlayer、QVideoWidget等关键类,极大简化了多媒体应用的开发流程。这一技术组合在工程实践中展现出显著价值,能够高效构建稳定、可扩展的桌面应用程序。其典型应用场景包括本地媒体播放器、视频会议客户端、监控系统界面等需要处理音视频流并具备友好交互的软件。本文聚

weixin_29009401的博客 243

操作系统接口设计(课程设计)

本文档谨供你参考,不要完全照抄!程序还是要自己写的,不然就没有意义了!你自己的事,自己决定吧! 利用C语言、DOS中断中21H与屏幕显示相关的中断调用完成设计,具体包括 1. 命令解释器 2. 列目录命令 3. 显示时间命令 4. 显示日期命令 5. 回显字符串命令 6. 创建目录命令 7. 删除目录命令 8. 更改路径命令 9. 显示当前工作目录命令 10. 删除文件命令 11. 打印文本命令 12. 文件重新命名 13. 显示文本命令 14. 显示版本命令 15. 显示目录结构命令 16. 清除当前显示内容命令 上述内容中,所有命令通过命令解释器能够执行,即启动命令解释器以后,输入相应命令,按照输入指令执行相应功能,并在屏幕上显示相应结果。

什么是PCB抄板?主要抄板软件有哪些?

PCB抄板,是在已经有电子产品实物和电路板实物的前提下,利用反向研发技术手段对电路板进行逆向解析,将原有产品的PCB文件、物料清单(BOM)文件、原理图文件等技术文件以及PCB丝印生产文件进行1:1的还原,然后再利用这些技术文件和生产文件进行PCB制版元器件焊接、飞针测试、电路板调试,完成原电路板样板的完整复制。   主要抄板软件有: 1、QuickPcb2005 Quic

8824

各类考试报名照片在线审核工具

包含各省省考报名照片、自考报名、二建报名、会计资格、人事考试、护士资格、卫生资质等网上报名照片审核处理工具

「阅读」APP 免费小说书源:从导入到长期维护的完整指南

「阅读」APP 精品书源是一个开源项目,为「阅读」APP 免费提供网络小说书源。导入一次书源,就能在 APP 里直接搜索、阅读各大小说网站的海量书籍,不用再在多个平台之间来回切换。 [![「阅读」APP 书源导入操作步骤](https://raw.gitcode.com/gh_mirrors/yu/Yuedu/raw/3f80aa9fe2d79464627fc8775efdde9ba83b43d

gitblog_00232的博客 283

DOS画画_操作系统课程设计_操作系统图形显示程序接口设计

用C写的DOS画画,需要有DOS环境下运行,windows的需要装dos虚拟机,编译需要用TC3.0里编译

中国人事考试网照片处理工具.rar

中国人事考试网照片处理工具

QT中的QImage与QPixmap区别

总结来说,`QImage`更适合于图像的处理和操作,而`QPixmap`更适合于图像的显示和图形界面的绘制。- `QPixmap`不适用于像素级别的操作,如果需要修改图像的像素数据,通常先将`QPixmap`转换为`QImage`,修改完成后,再转换回`QPixmap`。- `QImage`是一个用于处理图像数据的类,它提供了访问图像每个像素的接口,并且可以在图像上进行像素级别的操作。在Qt中,`QImage`和`QPixmap`都是用于处理图像数据的类,但它们在设计上有所不同,适用于不同的用途。

qq_43517257的博客 1232

手把手教你用CEFSharp打造防关联指纹浏览器(附完整代码)

本文详细介绍了如何基于CEFSharp框架,从零开始构建一个企业级的防关联指纹浏览器。通过剖析核心架构、多进程隔离实现、动态指纹混淆(如Canvas、WebGL)的实战代码,以及代理集成与性能优化,为跨境电商、广告投放等场景提供了一套可控、安全的开源解决方案。

fern8的博客 961

【OpenCV入门】详解图像读取、显示与保存的cv2.imread/imshow/imwrite函数用法

然而,在某些特殊情况下,如仅需分析图像轮廓或进行二值化处理时,可以使用灰度模式读取图像,这样可以减少数据维度并提升处理效率。值得注意的是,若指定的文件路径无效或文件格式不受支持,函数将返回空矩阵,因此在实际应用中必须加入错误处理机制。需要注意的是,保存操作不会改变原始图像矩阵的数据格式,但某些格式(如JPEG)会自动将浮点数图像转换为整数类型。在实际项目中,这三个功能通常形成完整的工作流:首先从存储设备读取原始图像,然后进行一系列处理操作,最终将结果同时显示在界面并保存到新文件。

恋上蓝花楹的博客 470

MIPI CSI-2数据加扰技术解析:LFSR实现与多通道同步策略

本文深入解析了MIPI CSI-2协议中的数据加扰技术,该技术通过线性反馈移位寄存器(LFSR)对图像数据进行随机化处理,旨在有效降低高速传输中的电磁干扰。文章详细阐述了LFSR的工作原理、D-PHY与C-PHY物理层不同的初始化与同步策略,并探讨了多通道同步的实现方法,为高速接口的EMC设计与调试提供了实践指导。

weixin_29207149的博客 384

为什么你的SSE连接总断?深入对比EventSource与fetch-event-source的5大差异点

本文深入对比了原生EventSource与@microsoft/fetch-event-source库在SSE连接稳定性上的五大关键差异。重点剖析了后者在请求能力、连接控制、错误处理、数据解析及性能优化方面的优势,特别是其支持POST请求、自定义Header、精细化的重连策略与生命周期管理,为解决生产环境中SSE连接频繁断开的问题提供了专业方案。

weixin_28077113的博客 494

本地部署全格式文件转换工具:从环境搭建到API集成实战指南

文件格式转换是数据处理中的基础需求,涉及视频、音频、图像和文档等多种媒体类型的互操作。其核心原理是通过编解码器对文件进行解析、转码和重新封装,实现跨平台兼容性。这项技术的价值在于打破格式壁垒,提升工作流自动化程度。在应用场景上,本地部署方案尤其适合对数据隐私敏感、需要批量处理或希望深度集成到自有系统的用户。本文聚焦于一个支持全格式转换的开源工具,详细介绍了其本地部署流程、资源占用优化和API集成方法,涵盖FFmpeg环境配置、批量任务队列设计等关键热词,为开发者构建自主可控的媒体处理能力提供实践参考。

weixin_34167819的博客 408

小波变换(Wavelet Transform)原理及介绍

小波变换是一种多分辨率时频分析工具,克服了傅里叶变换无法定位时间信息的缺陷。通过可伸缩平移的母小波,它能同时捕捉信号的高频细节(短时特征)和低频轮廓(整体趋势)。其核心原理是利用不同尺度的小波基与信号进行内积运算,生成时频域系数矩阵。小波变换具有动态分辨率特性,高频部分时间精度高,低频部分频率分辨率好。主要应用包括瞬态事件检测、信号降噪、数据压缩(如JPEG2000)和压缩感知(如快速MRI)。

qq_42758052的博客 5485

CBconvert:终极漫画格式转换神器,一站式解决10+格式兼容问题

还在为不同设备无法打开特定漫画格式而烦恼吗?CBconvert作为一款专业的漫画格式转换工具,为你提供全方位的数字漫画管理解决方案。无论你拥有CBR、CBZ、PDF还是EPUB格式的漫画文件,CBconvert都能轻松实现格式转换、图像优化和批量处理,让你的漫画收藏在任何设备上都能完美呈现。这款漫画转换工具支持10多种格式互转,是漫画爱好者必备的数字资产管理神器。 ## 为什么你需要CBconv

gitblog_01033的博客 200

异常数据处理——3σ原则、箱线图

异常数据处理——3σ原则、箱线图 用不包括异常值的剩余数据的均值替换、删除DataFrame中某列有异常值的整行等方法

杨老师的小跟班(*∩_∩*) 9033

【Python】秋招机考 —— 2025/9/17 华为 AI 方向

秋招机考,华为 AI,使用 Python 完成编程。

一杯水果茶!足矣~ 1165

python窗口显示图片imread() imshow()_opencv-python imread、imshow浏览目录下的图片文件

opencv-python imread、imshow浏览目录下的图片文件

weixin_39662462的博客 631

Python爬虫教程:抓取社交媒体内容(以Facebook和Instagram为例)

为什么抓取社交媒体内容?品牌分析与监测:通过抓取品牌、产品或服务相关的社交媒体数据,可以获得公众对其的评价,识别市场趋势,制定营销策略。情感分析:分析用户在社交平台上的情感倾向,如对某事件、产品、服务的正面或负面情绪。舆情监控:通过抓取大量社交媒体内容,监测公众对热点事件的反应,发现潜在的社会问题。用户行为分析社交媒体的数据类型:帖子和内容:包括用户发布的文本、图片、视频等。用户互动用户信息:包括用户名、粉丝数、关注数等社交信息。社交媒体的反爬虫机制:API限制。

2201_76125261的博客 1338
上一篇: 多么痛的领悟---关于RMB数据类型导致的元转分分转元的bug
下一篇: Springboot系列:Springboot与Thymeleaf模板引擎整合基础教程(附源码)
ggabcda
博客等级 码龄9年 130粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值