刚开始学习,记忆不是很好,容易忘,边学边记,阅读的速度会比较慢,看的会比较仔细。
详细请看:
David Herrmann’s Blog: Linux DRM Mode-Setting API
David Herrmann’s Github: drm-howto/modeset.c
modeset.c文件
源代码在上面可获取,原先备注是英文的,我就简单用翻译软件翻译一下,有错请指出:
/
如何设置DRM模式
本篇文档主要描述了关于DRM模式的应用编程接口,在使用之前应该包含 xf86drm.h和xf86drmMode.h
这两个头文件,默认在libdrm的每个默认主要发行版都会提供,且依赖性很小。
请忽略前面这些后面会用到的函数声明,我重新编排了这些函数,以便于能够从头到尾的顺序阅读本篇
文档,若你想要重新实现它的话,可以重新编排一下函数的位置从而去掉前面这些讨厌的函数声明。
为了便于阅读,我们忽略关于malloc()和friends的一些内存分配错误。
本篇文章所有的函数和全局变量都用“modest_*”作为前缀。故可知函数是否是本地helper还是由外部库
提供的。
/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h> //open
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h> //usr/include
#include <xf86drmMode.h> //usr/include
struct modeset_dev;
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
struct modeset_dev *dev);
static int modeset_create_fb(int fd, struct modeset_dev *dev);
static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
struct modeset_dev *dev);
static int modeset_open(int *out, const char *node);
static int modeset_prepare(int fd);
static void modeset_draw(void);
static void modeset_cleanup(int fd);
/
当linux内核检测到你的机器上有显卡,它加载正确的设备驱动程序(位于内核树。
/drivers/gpu/drm/<xy>)并提供两个字符设备来控制它。Udev(或者任何热插拔的应用程序)
将创建它们为:
/dev/dri/card0
/dev/dri/controlID64
我们只需要第一个。您可以将此路径硬编码到应用程序中就像我们在这里做的,但建议
使用libudev与真正的热插拔和多座支持。但是,这超出了本文的范围。
还要注意的是,如果你有多个显卡,也可能有:
/dev/ driver /card1, /dev/ driver /card2,…
modeset_open(out, node): 该函数用于打开DRM设备表示为:@node.
成功后,新的fd将存储在@out中。失败,返回一个负的错误代码。
/
static int modeset_open(int *out, const char *node)
{
int fd, ret;
uint64_t has_dumb;
fd = open(node, O_RDWR | O_CLOEXEC); /打开node文件,正常返回文件描述符,失败返回-1
if (fd < 0) { /失败输出错误信息
ret = -errno;
fprintf(stderr, "cannot open '%s': %m\n", node);
return ret;
}
if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb) {
fprintf(stderr, "drm device '%s' does not support dumb buffers\n",node);
close(fd);
return -EOPNOTSUPP;
}
*out = fd;
return 0;
}
/
下一步我们需要找到可用的显示设备。libdrm提供drmModeRes结构,包含所有需要的信息。
我们可以通过drmModeGetResources(fd)检索它,并通过drmModeFreeResources (res)释放它。
显卡上的物理连接器称为“连接器”(connector)。你可以插入显示器并可控制其显示内容。
我们肯定对现在使用的是哪个连接器感兴趣,因此我们简单地刷新连接器列表,并尝试在可用的显
示屏上显示测试图片。
然而,这并不像听起来那么容易。
首先,我们需要检查连接器是否实际使用(显示器插入并打开)。
然后,我们需要找到一个可以控制这个连接器的CRTC(CRTC之后再描述)
之后我们创建一个framebuffer对象。如果我们拥有这一切,我们可以mmap()帧缓冲区,并
在其中绘制一个测试图片。然后我们可以告诉DRM设备使用选定的连接器在给定的CRTC上显示framebuffer。
当我们想要在framebuffer上绘制移动的图片时,我们必须这样做记住所有这些设置。因此,
我们创建一个"struct modeset_dev"对象的每个成功的connector+crtc+framebuffer对初始化
并将其推入全局设备列表。
这种结构的每个字段在第一次使用时都会被描述。作了一个总结:
"struct modeset_dev"包含:{
- @next:指向单链表中的下一个设备
- @width:缓冲区对象的宽度
- @height:缓冲区对象的高度
- @stride:缓冲区对象的stride值
- @size:内存映射缓冲区的大小
- @handle:我们可以绘制的缓冲区对象的DRM句柄
- @map:指向内存映射缓冲区的指针
- @mode:我们想使用的显示模式
- @fb:一个缓冲对象作为扫描缓冲区的framebuffer句柄
- @conn:我们想在这个缓冲区中使用的连接器ID
- @crtc:我们想在这个连接器中使用的crtc ID
- @saved_crtc:我们更改crtc之前的配置。我们使用它这样我们可以在退出时恢复相同的模式。
}
/
struct modeset_dev {
struct modeset_dev *next; /指向下一个设备指针
uint32_t width; /缓冲区对象的宽度
uint32_t height; /缓冲区对象的高度
uint32_t stride; /缓冲区对象的步幅
uint32_t size; /缓冲区对象的大小
uint32_t handle; /句柄
uint8_t *map; /指向内存映射缓冲区的指针
drmModeModeInfo mode; /显示模式
uint32_t fb; /framebuffer
uint32_t conn; /连接器ID
uint32_t crtc; /crtc ID
drmModeCrtc *saved_crtc; /更改crtc前的配置
};
static struct modeset_dev *modeset_list = NULL;
/
因此,下一步我们需要实际准备所有找到的连接器(下面这个函数)。
modeset_prepare(fd):该函数接受DRM fd作为参数和然后简单地从设备检索资源信息。
然后迭代通过所有连接器,并调用其他辅助函数来初始化这个连接器(稍后描述)。
如果初始化成功,我们只需添加这个对象作为新设备进入全局modesset设备列表。
资源结构包含一个所有connector- id的列表。我们使用函数drmModeGetConnector()
检索更多信息的每个连接器。在我们用完它之后,我们再次释放它drmModeFreeConnector()。
modeset_setup_dev()返回-ENOENT如果连接器是当前未使用且未插入显示器。可忽略这个连接器。
/
static int modeset_prepare(int fd)
{
drmModeRes *res;
drmModeConnector *conn;
int i;
struct modeset_dev *dev;
int ret;
/----------检索资源----------/
res = drmModeGetResources(fd);
if (!res) { /检索失败则报错
fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",errno);
return -errno;
}
/----------遍历所有的连接器----------/
for (i = 0; i < res->count_connectors; ++i) {
/----------获取每个连接器的信息----------/
conn = drmModeGetConnector(fd, res->connectors[i]);
if (!conn) {
fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
i, res->connectors[i], errno);
continue;
}
/----------创建设备结构----------/
dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->conn = conn->connector_id;
/----------调用函数来准备这个连接器(连接器的准备)----------/
ret = modeset_setup_dev(fd, res, conn, dev);
if (ret) {
if (ret != -ENOENT) {
errno = -ret;
fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n",
i, res->connectors[i], errno);
}
free(dev);
drmModeFreeConnector(conn);
continue;
}
/----------释放 连接器数据和链接设备到全局列表----------/
drmModeFreeConnector(conn);
dev->next = modeset_list;
modeset_list = dev;
}
/----------再次释放资源----------/
drmModeFreeResources(res);
return 0;
}
/
现在我们深入研究如何设置单个连接器。如前所述,我们首先需要检查几件事:
1.若连接器目前未使用,即没有插入监视器,我们可以忽略它。
2.我们必须找到一个合适的分辨率(resolution)和刷新率(refresh-rate)
这一切都是可在每个crtc的drmModeModeInfo结构保存。我们只是使用第一种模式。
这总是与模式最高的分辨率。
但是,在实际的应用程序中应该进行更复杂的模式选择。
3.我们需要找到一个可以驱动这个连接器的CRTC。CRTC是每个显卡的内部资源。
crtc的数量控制着可以独立控制的连接器的数量。也就是说,一个显卡可能比crtc有更
多的连接器,这意味着不是所有的监视器都可以被独立控制。
如果监视器显示相同的内容,实际上可以通过一个CRTC控制多个连接器。但是,我
们在这里不使用它。
把连接器想象成连接监视器和crtc是管理哪个数据进入哪个管道的控制器。如果
管道比crtc多,我们就不能同时控制所有管道。
4.我们需要为这个连接器创建一个framebuffer。framebuffer是一个我们可以写入
XRGB32数据的内存缓冲区。因此,我们使用它来呈现我们的图形,然后CRTC可以将数据
从帧缓冲区扫描到监视器上。
/
static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
struct modeset_dev *dev)
{
int ret;
/----------检查是否有显示器连接----------/
if (conn->connection != DRM_MODE_CONNECTED) {
fprintf(stderr, "ignoring unused connector %u\n",
conn->connector_id);
return -ENOENT;
}
/----------检查是否至少有一个有效的模式----------/
if (conn->count_modes == 0) {
fprintf(stderr, "no valid mode for connector %u\n",
conn->connector_id);
return -EFAULT;
}
/----------复制模式信息到我们的设备结构----------/
memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
dev->width = conn->modes[0].hdisplay;
dev->height = conn->modes[0].vdisplay;
fprintf(stderr, "mode for connector %u is %ux%u\n",
conn->connector_id, dev->width, dev->height);
/----------为这个连接器找到一个crtc----------/
ret = modeset_find_crtc(fd, res, conn, dev);
if (ret) {
fprintf(stderr, "no valid crtc for connector %u\n",
conn->connector_id);
return ret;
}
/----------为这个CRTC创建一个framebuffer----------/
ret = modeset_create_fb(fd, dev);
if (ret) {
fprintf(stderr, "cannot create framebuffer for connector %u\n",
conn->connector_id);
return ret;
}
return 0;
}
/
modeset_find_crtc(fd, res, conn, dev):该函数试图为给定的连接器找到合适的CRTC。实际上,
我们必须再引入一个DRM对象来让这个问题更加清楚:编码器(Encoders)。
编码器帮助CRTC将数据从framebuffer转换为可用于所选连接器的正确格式。我们不需要了解更多的
这些转换来使用它。但是,您必须知道每个连接器都有一个它可以使用的编码器的有限列表。每个编码器只
能与有限的crtc列表一起工作。所以我们要做的是尝试每一个编码器是可用的,并寻找一个CRTC,这个编码
器可以工作。如果我们找到了第一个可行的组合,我们会很高兴地把它写入@dev结构中。
但在迭代所有可用的编码器之前,我们首先在连接器上尝试当前活动的encoder+crtc,以避免完整的
模式集。
但是,在我们使用CRTC之前,我们必须确保没有其他设备(我们之前设置的设备)已经在使用这个CRTC。
请记住,每个CRTC只能驱动一个连接器!因此,我们只需遍历以前设置设备的“modeset_list”,并检查这个
CRTC以前是否使用过。否则,我们继续使用下一个CRTC/Encoder组合。
/
static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
struct modeset_dev *dev)
{
drmModeEncoder *enc;
int i, j;
uint32_t crtc;
struct modeset_dev *iter;
/----------首先尝试当前连接的编码器+crtc----------/
if (conn->encoder_id)
enc = drmModeGetEncoder(fd, conn->encoder_id);
else
enc = NULL;
if (enc) {
if (enc->crtc_id) {
crtc = enc->crtc_id;
for (iter = modeset_list; iter; iter = iter->next) {
if (iter->crtc == crtc) {
crtc = -1;
break;
}
}
if (crtc >= 0) {
drmModeFreeEncoder(enc);
dev->crtc = crtc;
return 0;
}
}
drmModeFreeEncoder(enc);
}
/
如果连接器目前没有绑定到一个编码器,或者encoder+crtc已经被另一个连接器使用
(实际上不太可能,但让我们安全),迭代所有其他可用的编码器来找到匹配的crtc。
/
for (i = 0; i < conn->count_encoders; ++i) {
enc = drmModeGetEncoder(fd, conn->encoders[i]);
if (!enc) {
fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
i, conn->encoders[i], errno);
continue;
}
/----------迭代所有全局CRTCs----------/
for (j = 0; j < res->count_crtcs; ++j) {
/----------检查此CRTC是否与编码器一起工作----------/
if (!(enc->possible_crtcs & (1 << j)))
continue;
/----------检查是否有其他设备已经使用此CRTC----------/
crtc = res->crtcs[j];
for (iter = modeset_list; iter; iter = iter->next) {
if (iter->crtc == crtc) {
crtc = -1;
break;
}
}
/----------我们已经找到一个CRTC,所以保存它并返回----------/
if (crtc >= 0) {
drmModeFreeEncoder(enc);
dev->crtc = crtc;
return 0;
}
}
drmModeFreeEncoder(enc);
}
fprintf(stderr, "cannot find suitable CRTC for connector %u\n",
conn->connector_id);
return -ENOENT;
}
/
modeset_create_fb(fd, dev):在我们找到crtc+连接器+模式组合后,我们需要实际创建一个
合适的框架缓冲区,我们可以使用它。实际上有两种方法可以做到这一点:
方法一:我们可以创建一个所谓的“哑缓冲区(dumb buffer)”。这是一个我们可以通过
mmap()内存映射的缓冲区,每个驱动程序都支持它。我们可以使用它在CPU上进行非加速的软件渲染。
方法二:我们可以使用libgbm创建用于硬件加速的缓冲区。libgbm是一个抽象层,它为每
个可用的DRM驱动程序创建这些缓冲区。由于没有通用API,每个驱动程序都提供了自己的方式来创建
这些缓冲区。然后我们可以使用这样的缓冲区来创建OpenGL上下文与mesa3D库。
我们在这里使用第一种解决方案,因为它简单得多,而且不需要任何外部库。然而,如果你想通
过OpenGL使用硬件加速,实际上用libgbm和libEGL创建缓冲区是相当容易的。但这超出了本文的讨论范围。
所以我们要做的是从驱动程序请求一个新的哑缓冲区(dumb buffer)。我们指定的大小与我们为
连接器选择的当前模式相同。然后我们请求驱动程序为内存映射准备这个缓冲区。之后,我们执行实际
的mmap()调用。所以我们现在可以通过dev->map内存map直接访问framebuffer内存。
/
static int modeset_create_fb(int fd, struct modeset_dev *dev)
{
struct drm_mode_create_dumb creq;
struct drm_mode_destroy_dumb dreq;
struct drm_mode_map_dumb mreq;
int ret;
/----------创建dumb buffer----------/
memset(&creq, 0, sizeof(creq));
creq.width = dev->width;
creq.height = dev->height;
creq.bpp = 32;
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
if (ret < 0) {
fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
errno);
return -errno;
}
dev->stride = creq.pitch;
dev->size = creq.size;
dev->handle = creq.handle;
/----------为dumb-buffer创建framebuffer对象----------/
ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride,
dev->handle, &dev->fb);
if (ret) {
fprintf(stderr, "cannot create framebuffer (%d): %m\n",
errno);
ret = -errno;
goto err_destroy;
}
/----------为内存映射准备缓冲区----------/
memset(&mreq, 0, sizeof(mreq));
mreq.handle = dev->handle;
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
if (ret) {
fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
errno);
ret = -errno;
goto err_fb;
}
/----------执行实际的内存映射----------/
dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, mreq.offset);
if (dev->map == MAP_FAILED) {
fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
errno);
ret = -errno;
goto err_fb;
}
/----------将framebuffer清除为0----------/
memset(dev->map, 0, dev->size);
return 0;
err_fb:
drmModeRmFB(fd, dev->fb);
err_destroy:
memset(&dreq, 0, sizeof(dreq));
dreq.handle = dev->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
return ret;
}
/
终于!我们有一个连接器与合适的CRTC。我们知道我们想要使用哪种模式,并且我们有一个
大小正确的framebuffer可以写入。没有什么特别的事情要做了。我们只需要对CRTC进行编程,
使其将每个新的framebuffer连接到我们保存在全局modeset_list中的每个组合的每个选定连
接器。这是通过调用drmModeSetCrtc()来完成的。
现在我们准备好使用main()函数了。首先,我们检查用户是否在命令行上指定了DRM设备,
否则我们使用默认的/dev/ drim /card0。然后通过modeset_open()打开设备。
modeset_prepare()准备所有连接器,我们可以循环遍历“modeset_list”,并在每个CRTC/连
接器组合上调用drmModeSetCrtc()。但是打印空白的黑色页面很无聊,所以我们有另一个
函数modeset_draw(),它在framebuffer中绘制一些颜色,持续5秒,然后返回。然后,我们有
所有的清理功能,正确地释放所有设备后,我们使用它们。所有这些函数都在main()函数下面进行了描述。
附注:drmModeSetCrtc()实际上接受一个我们想要用这个CRTC控制的连接器列表。但是,我们
只传递了一个连接器。如前所述,如果我们使用多个连接器,那么所有连接器都将具有相同的控制
framebuffer,因此输出将被克隆。这通常不是你想要的,所以我们避免在这里解释这个特性。此外,
所有连接器都必须以相同的模式运行,这通常也不能保证。因此,每个CRTC只使用一个连接器。
在调用drmModeSetCrtc()之前,我们还保存当前的CRTC配置。这是在modeset_cleanup()中使
用的,用于将CRTC恢复到更改之前的相同模式。
如果我们不这样做,在我们退出后,屏幕将保持空白,直到另一个应用程序执行modesetting本身。
/
int main(int argc, char **argv)
{
int ret, fd;
const char *card;
struct modeset_dev *iter;
/----------检查打开哪个DRM设备----------/
if (argc > 1)
card = argv[1];
else
card = "/dev/dri/card0";
fprintf(stderr, "using card '%s'\n", card);
/----------打开DRM设备---------/
ret = modeset_open(&fd, card);
if (ret)
goto out_return;
/----------准备好所有连接器和crtcs----------/
ret = modeset_prepare(fd);
if (ret)
goto out_close;
/----------对每个找到的连接器+CRTC执行实际的模式设置(modesset)----------/
for (iter = modeset_list; iter; iter = iter->next) {
iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0,
&iter->conn, 1, &iter->mode);
if (ret)
fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
iter->conn, errno);
}
/----------画一些颜色5秒----------/
modeset_draw();
/----------清理所有的----------/
modeset_cleanup(fd);
ret = 0;
out_close:
close(fd);
out_return:
if (ret) {
errno = -ret;
fprintf(stderr, "modeset failed with error %d: %m\n", errno);
} else {
fprintf(stderr, "exiting\n");
}
return ret;
}
/-------------------------------------------------------
一个简短的辅助函数来计算变化的颜色值。没必要去理解它
/-------------------------------------------------------
static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
{
uint8_t next;
next = cur + (*up ? 1 : -1) * (rand() % mod);
if ((*up && next < cur) || (!*up && next > cur)) {
*up = !*up;
next = cur;
}
return next;
}
/
modeset_draw():在所有已配置的framebuffer中绘制纯色。每100毫秒,颜色就会变化为一种稍微
不同的颜色,所以我们会得到某种平滑变化的颜色梯度。
颜色计算可以忽略,因为它很无聊。有趣的是遍历"modeset_list"然后遍历所有的行和宽。然后我们
将每个像素分别设置为当前的颜色。
我们会在每次重划后100毫秒睡觉时这样做50次。这使得50*100ms = 5000ms = 5s,所以需要5秒才
能完成这个循环。
请注意,我们直接在framebuffer中绘制。这意味着当我们重新绘制屏幕时,当显示器刷新时,您将看
到闪烁。为了避免这种情况,您需要使用两个帧缓冲区和对drmModeSetCrtc()的调用来在两个缓冲区之间进行切换。
你也可以使用drmModePageFlip()来做垂直同步的页面翻转。但这超出了本文的讨论范围。
/
static void modeset_draw(void)
{
uint8_t r, g, b;
bool r_up, g_up, b_up;
unsigned int i, j, k, off;
struct modeset_dev *iter;
srand(time(NULL));
r = rand() % 0xff;
g = rand() % 0xff;
b = rand() % 0xff;
r_up = g_up = b_up = true;
for (i = 0; i < 50; ++i) {
r = next_color(&r_up, r, 20);
g = next_color(&g_up, g, 10);
b = next_color(&b_up, b, 5);
for (iter = modeset_list; iter; iter = iter->next) {
for (j = 0; j < iter->height; ++j) {
for (k = 0; k < iter->width; ++k) {
off = iter->stride * j + k * 4;
*(uint32_t*)&iter->map[off] =
(r << 16) | (g << 8) | b;
}
}
}
usleep(100000);
}
}
/------------------------------------------------------------
modeset_cleanup(fd): 这将清理我们在此期间创建的所有设备
modeset_prepare(). 它将crtc重置为它们保存的状态,并释放所有内存。
这一切是如何运作的应该很明显了。
/------------------------------------------------------------
static void modeset_cleanup(int fd)
{
struct modeset_dev *iter;
struct drm_mode_destroy_dumb dreq;
while (modeset_list) {
/----------从全局列表中删除----------/
iter = modeset_list;
modeset_list = iter->next;
/----------恢复保存的CRTC配置----------/
drmModeSetCrtc(fd,
iter->saved_crtc->crtc_id,
iter->saved_crtc->buffer_id,
iter->saved_crtc->x,
iter->saved_crtc->y,
&iter->conn,
1,
&iter->saved_crtc->mode);
drmModeFreeCrtc(iter->saved_crtc);
/---------- unmap buffer----------/
munmap(iter->map, iter->size);
/----------删除framebuffer----------/
drmModeRmFB(fd, iter->fb);
/----------删除dumb buffer----------/
memset(&dreq, 0, sizeof(dreq));
dreq.handle = iter->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
/----------自由分配的内存(释放分配的内存)----------/
free(iter);
}
}
/
我希望这是对DRM modesetting API简短但简单的概述。DRM API提供了更多的功能,包括:
-双缓冲或三缓冲(或任何你想要的)
-vsync使用翻页
-硬件加速渲染(例如通过OpenGL)
-输出克隆
-图形客户端+认证
- DRM飞机/覆盖/精灵
-...
如果你对这些主题感兴趣,我目前只能将你重定向到现有的实现,包括:
-plymouth(它使用了像这个例子一样的哑缓冲区;非常容易理解)
-kmscon(使用libuterm来做这个)
-wayland(非常复杂的DRM渲染器;很难完全理解,因为它使用更复杂的技术,如DRM飞机)
-xserver(很难理解,因为它被分割在很多文件/项目中)
但是理解modesset(如本文档所述)是如何工作的,这对于理解所有后续的DRM主题是至关重要的。
欢迎任何反馈。您可以自由地将这些代码用于自己的文档或项目。
-由http://github.com/dvdhrm/docs主持
-David Herrmann <dh.herrmann@googlemail.com>
open函数:打开或创建一个文件
头文件:#include <fcntl.h>
函数原型:int open(const char _pathname, int oflag, … /_mode_t mode_/ )
——返回值
:若成功过则返回文件描述符,若出错则返回-1.
——pathname
:打开或创建文件的名字。
——oflag
:可用来说明此函数的多个选项。由下列一个或多个常量进行“或”运算构成oflag参数。
——…
表示余下参数的数量及其类型根据具体调用会有所不同。
O_RDONLY //0
:只读打开
O_WRONLY //1
:只写打开
O_RDWR //2
:读、写打开===============以上三个必选其一,且需只可选一=================
O_CLOEXEC
:在进程执行exec系统调用时,关闭此打开的文件描述符,为原子操作。防止父进程泄露打开的文件给子进程,即便子进程没有相应权限。
O_APPEND
:每次写时都追加到文件的尾端
O_CREAT
:若此文件不存在,则创建它。使用此选项时,需第三个参数mode,用其指定该新文件的访问权限位。
O_EXCL
:若同时存在O_CREAT且文件已存在则出错,可用测试文件是否存在,若不存在则创建次文件,使测试和创建两者成为一个原子操作。
O_TRUNC
:若此文件存在,且为只写或读写成功打开,则将其长度截短为0.
O_NOCTTY
:若pathname为终端设备,则不将该设备分配作为此进程的控制终端。
…
:还有很多!!!
fprintf()函数:发送格式化输出到流 stream 中
头文件:#include <stdio.h>
函数原型:int fprintf ( FILE _stream, char _ format, … );
——返回值
:返回成功写入的字符的个数,失败则返回负数
——stream
: 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
——format
:这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
——…
:参数列表
关于 stdin、stdout、stderr 的说明如下:
stdin:标准输入设备
stdout:标准输出设备(默认输出屏幕)
stderr:标准错误输出设备(默认输出屏幕)
详细请查看:
super_marieCSDN-详解C语言中的stdin,stdout,stderr
RUNOOB.COM-C 库函数 - fprintf()
C语言中文网-格式化读写文件
drmGetCap()函数:获取
头文件:#include<xf86drm.h>
函数原型:
DRM的能力通过drmGetCap接口获取,用drm_get_cap结构描述:
/** DRM_IOCTL_GET_CAP ioctl argument type */
struct drm_get_cap {
__u64 capability;
__u64 value;
};
int drmGetCap(int fd, uint64_t capability, uint64_t *value)
{
struct drm_get_cap cap;
int ret;
memclear(cap);
cap.capability = capability;
ret = drmIoctl(fd, DRM_IOCTL_GET_CAP, &cap);
if (ret)
return ret;
*value = cap.value;
return 0;
}
Makefile文件
FLAGS=`pkg-config --cflags --libs libdrm`
FLAGS+=-Wall -O0 -g
FLAGS+=-D_FILE_OFFSET_BITS=64
all:
gcc -o modeset modeset.c $(FLAGS)
gcc -o modeset-double-buffered modeset-double-buffered.c $(FLAGS)
gcc -o modeset-vsync modeset-vsync.c $(FLAGS)
参考自:jim.liCSDN-Makefile好助手:pkgconfig、jim.liCSDN-系统程序员成长计划-工程管理(三)
情景:
在Unix开发软件情境下,在本机上可以成功编译运行,但在别人主机上却编译不成功(操作系统相同,且对应库都安装上了),究其原因发现,这是由于两人库的安装路径不相同导致的。而为了避免这样的情况,便可使用pkg-config
语句解决这类问题。
功能:
①检查库版本号。若不满足,打印错误信息,避免连接错误版本库文件
②获得编译预处理参数,如:宏定义,头文件位置。
③获取链接参数,如:库及依赖的其他库的位置,文件名及其它的一些连接参数。
④自动加入所依赖的其他库的设置
归纳:使用pkg-config
后,自动实现:查询指定软件包 的配置信息,如软件包的名称、说明、版本号、头文件、库和依赖关系等等,库文件安装哪里都没关系。
说明:
pkg-config
并不是凭空实现功能,为了让pkg-config
可得到这些信息,要求库提供者提供一个.pc文件,如这边的libdrm.pc
:(我的该文件路径为:/usr/lib/x86_64-linux-gnu/pkgconfig/libdrm.pc
)
注意:使用pkg-config
需提供两个参数:—cflags —libs
将所需信息提取出来供编译和连接使用(可看下面.pc文件中就有这两个参数)
prefix=/usr
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include
Name: libdrm
Description: Userspace interface to kernel DRM services
Version: 2.4.102
Libs: -L${libdir} -ldrm
Cflags: -I${includedir} -I${includedir}/libdrm
.pc文件说明:
前面是定义些变量,后面是些关键字。
Name: 名称
Description: 功能描述
URL: 用户可以通过该URL获得更多信息或下载信息(可选)
Version: 版本号
Requires: 所依赖的软件包(可选)
Requires.private: 所依赖的软件包且无需第三方知道(可选)
Conflicts: 有没有和别的模块冲突。(可选)
Libs: 调用者的链接参数。(pkg-config的参数–libs就指向这里,主要写本模块的库/依赖库的路径)
Libs.private: 本模块依赖的库,但不需要第三方知道。
Cflags: 调用者的编译参数。(pkg-config的参数–cflags就指向这里。主要用于写本模块的头文件的路径。 )
还想在深入了解的话,可看:jim.liCSDN-系统程序员成长计划-工程管理(三)
.pc文件存放路径:
该文件通常放在:这个文件一般放在/usr/lib/pkgconfig/
或者/usr/local/lib/pkgconfig/
里,当然也可以放在其它任何地方,如像X11相关的pc文件是放在/usr/X11R6/lib/pkgconfig
下的。
为了让pkgconfig可以找到你的pc文件,你要把pc文件所在的路径,设置在环境变量PKG_CONFIG_PATH
里。
pkg-config是一个linux下的命令,用于获得某一个库/模块的所有编译相关的信息
在终端中输入指令:pkg-config —cflags —libs libdrm
便能得到相关信息。
实现方式:
使用方法很简单,比如,我们要使用gtk+的库编译一个程序:
gcc -g arrow.c -o arrow \`pkg-config "gtk+-2.0 > 2.0.0" --cflags --libs`
modeset.c的实际操作
下载的具体步骤就不在赘述了(学习篇4也有)。
从图形化的Ubuntu系统下切换为服务器的Ubuntu系统,可使用快捷键(Ctrl+Alt+F1~F6),具体大家可百度以下,我这边F1、F2为图形化系统,F3 ~ F6均为服务器系统。
这时候你输入对应的用户名和密码(若密码一直不通过的话,你应该需要按一下键盘上的数码锁定键【Num Lock】)进入对应的路径,然后输入指令运行文件(指令看下面可看)。
实验现象就是会给屏幕填充颜色。
评论(0)
您还未登录,请登录后发表或查看评论