如何在 dev_t 和 major/minor 设备号之间进行转换?

How to convert between a dev_t and major/minor device numbers?

我正在尝试编写一个处理 ustar 档案的可移植程序。对于设备文件,这些档案存储 majorminor 设备编号。 然而, struct stat 如 POSIX 仅包含一个 st_rdev 类型 dev_t 的成员,描述为“设备 ID(如果文件是特殊字符或块)。”

如何以可移植的方式在一对主要和次要设备编号与 stat() 返回的单个 st_rdev 成员之间进行转换?

在定义 BSD_SOURCE 后使用 major()minor() 宏。

The makedev(), major(), and minor() functions are not specified in POSIX.1, but are present on many other systems.

http://man7.org/linux/man-pages/man3/major.3.html

虽然所有 POSIX 编程接口都按原样使用设备编号(dev_t 类型),但 FUZxxl 在对该答案的评论中指出,常见的 UStar 文件格式 -- 最常见的 tar 存档格式 -- 确实将设备编号分为主要和次要。 (它们通常每个编码为七个八进制数字,因此出于兼容性原因,应该限制为 21 位无符号主要和 21 位无符号次要。这也意味着将设备号映射到主要或次要不是可靠的方法。)

以下包括在 Jonathon Reinhart's answer 上扩展的文件,在网上挖掘各种系统手册页和文档(对于 makedev()major()minor() ), 加上对这个问题的评论。

#if defined(custom_makedev) && defined(custom_major) && defined(custom_minor)
/* Already defined */
#else

#undef custom_makedev
#undef custom_major
#undef custom_minor

#if defined(__linux__) || defined(__GLIBC__)
/* Linux, Android, and other systems using GNU C library */
#ifndef _BSD_SOURCE
#define _BSD_SOURCE 1
#endif
#include <sys/types.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#elif defined(_WIN32)
/* 32- and 64-bit Windows. VERIFY: These are just a guess! */
#define custom_makedev(dmajor, dminor) ((((unsigned int)dmajor << 8) & 0xFF00U) | ((unsigned int)dminor & 0xFFFF00FFU))
#define custom_major(devnum)           (((unsigned int)devnum & 0xFF00U) >> 8)
#define custom_minor(devnum)           ((unsigned int)devnum & 0xFFFF00FFU)

#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
/* FreeBSD, OpenBSD, NetBSD, and DragonFlyBSD */
#include <sys/types.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#elif defined(__APPLE__) && defined(__MACH__)
/* Mac OS X */
#include <sys/types.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#elif defined(_AIX) || defined (__osf__)
/* AIX, OSF/1, Tru64 Unix */
#include <sys/types.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#elif defined(hpux)
/* HP-UX */
#include <sys/sysmacros.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#elif defined(sun)
/* Solaris */
#include <sys/types.h>
#include <sys/mkdev.h>
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)

#else
/* Unknown OS. Try a the BSD approach. */
#ifndef _BSD_SOURCE
#define _BSD_SOURCE 1
#endif
#include <sys/types.h>
#if defined(makedev) && defined(major) && defined(minor)
#define custom_makedev(dmajor, dminor) makedev(dmajor, dminor)
#define custom_major(devnum)           major(devnum)
#define custom_minor(devnum)           minor(devnum)
#endif
#endif

#if !defined(custom_makedev) || !defined(custom_major) || !defined(custom_minor)
#error Unknown OS: please add definitions for custom_makedev(), custom_major(), and custom_minor(), for device number major/minor handling.
#endif

#endif

可以从现有的 UStar-format-capable 归档器中收集额外的定义。在我看来,与每个 OS/architecture 上现有实现的兼容性是这里最重要的事情。

以上应该涵盖所有使用GNU C库的系统,Linux(包括Android),FreeBSD,OpenBSD,NetBSD,DragonFlyBSD,Mac OS X, AIX、Tru64、HP-UX 和 Solaris,以及包含 <sys/types.h> 时定义宏的任何内容。对于 Windows 部分,我不确定。

据我了解,Windows 对所有普通文件使用设备 0,对设备使用 HANDLE(空指针类型)。我完全不确定上面的逻辑在 Windows 上是否合理,但是许多旧系统将设备号的 8 位最低有效位放入 minor,接下来的 8 位放入 major,惯例似乎是任何剩余的位也将被放入(不移动)到次要位置。检查现有的 UStar-format tar 档案并引用设备会很有用,但我个人根本不使用 Windows。

如果没有检测到系统,并且系统不使用BSD-style包含来定义宏,上述将出错停止编译。 (我个人会添加 compile-time 机制来帮助找到正确的 header 定义,使用例如 findxargsgrep,以防万一,建议也将附加内容发送到上游。touch empty.h ; cpp -dM empty.h ; rm -f empty.h 应该显示所有预定义的宏,以帮助识别 OS and/or C 库。)

最初,POSIX 声明 dev_t 必须是算术类型(因此,理论上,它可能是 floatdouble 的某种变体某些系统),但 IEEE Std 1003.1, 2013 Edition 表示它必须是整数类型。我敢打赌,这意味着没有任何已知的 POSIX-y 系统使用过 floating-point dev_t 类型。看起来 Windows 使用空指针,或 HANDLE 类型,但 Windows 不是 POSIX-compliant 无论如何。

我有一个基于 Minix 的 ls 古董版本的程序,但是从那时起我修改了很多 mangled。它有以下代码来检测主要和次要宏——以及一些关于它过去工作过的(现在)古董系统的评论。它假定 GCC 的最新版本可用于支持 #pragma GCC diagnostic ignored 等。您必须非常努力(例如 clang -Weverything)才能使 -Wunused-macros 选项生效,除非您明确包含它.

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-macros"
/* Defines to ensure major and minor macros are available */
#define _DARWIN_C_SOURCE    /* In <sys/types.h> on MacOS X */
#define _BSD_SOURCE         /* In <sys/sysmacros.h> via <sys/types.h> on Linux (Ubuntu 12.0.4) */
#define __EXTENSIONS__      /* Maybe beneficial on Solaris */
#pragma GCC diagnostic pop

/* From Solaris 2.6 sys/sysmacros.h
**
** WARNING: The device number macros defined here should not be used by
** device drivers or user software. [...]  Application software should make
** use of the library routines available in makedev(3). [...]  Macro
** routines bmajor(), major(), minor(), emajor(), eminor(), and makedev()
** will be removed or their definitions changed at the next major release
** following SVR4.
**
** #define  O_BITSMAJOR 7       -- # of SVR3 major device bits
** #define  O_BITSMINOR 8       -- # of SVR3 minor device bits
** #define  O_MAXMAJ    0x7f    -- SVR3 max major value
** #define  O_MAXMIN    0xff    -- SVR3 max major value
**
** #define  L_BITSMAJOR 14      -- # of SVR4 major device bits
** #define  L_BITSMINOR 18      -- # of SVR4 minor device bits
** #define  L_MAXMAJ    0x3fff  -- SVR4 max major value
** #define  L_MAXMIN    0x3ffff -- MAX minor for 3b2 software drivers.
** -- For 3b2 hardware devices the minor is restricted to 256 (0-255)
*/

/* AC_HEADER_MAJOR:
** - defines MAJOR_IN_MKDEV if found in sys/mkdev.h
** - defines MAJOR_IN_SYSMACROS if found in sys/macros.h
** - otherwise, hope they are in sys/types.h
*/

#if defined MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#elif defined MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#elif defined(MAJOR_MINOR_MACROS_IN_SYS_TYPES_H)
/* MacOS X 10.2 - for example */
/* MacOS X 10.5 requires -D_DARWIN_C_SOURCE or -U_POSIX_C_SOURCE - see above */
#elif defined(USE_CLASSIC_MAJOR_MINOR_MACROS)
#define major(x)    ((x>>8) & 0x7F)
#define minor(x)    (x & 0xFF)
#else
/* Hope the macros are in <sys/types.h> or otherwise magically visible */
#endif

#define MAJOR(x)    ((long)major(x))
#define MINOR(x)    ((long)minor(x))

您可能不会那么热衷于代码的 'hope the macros are … magically visible' 部分。

AC_HEADER_MAJOR的引用是对autoconf中推导出此信息的宏的引用。如果您有一个由 autoconf.

生成的 config.h 文件,这将是相关的

POSIX

注意POSIXpax command defines the ustar格式,并在信息中指定包含devmajordevminor ,但添加:

… Represent character special files and block special files respectively. In this case the devmajor and devminor fields shall contain information defining the device, the format of which is unspecified by this volume of POSIX.1-2008. Implementations may map the device specifications to their own local specification or may ignore the entry.

这意味着没有一种完全可移植的方式来表示数字。这并非完全不合理(但很麻烦);主要和次要设备编号的含义因平台而异,也未指定。任何通过 ustar 格式创建块或字符设备的尝试只有在源计算机和目标计算机是 运行 相同(相同版本)操作系统的情况下才能合理可靠地工作——尽管通常它们可以跨平台移植相同操作系统的版本。