1

我正在运行具有以下原型的Linux 3.2内核:ioctl

long ioctl(struct file *f, unsigned int cmd, unsigned long arg);

我注意到无论从相应用户空间函数传递给的实际数据类型如何,arg它总是无符号长。ioctl示例ioctl通常显示以下实现(source):

typedef struct
{
    int status, dignity, ego;
} query_arg_t;

#define QUERY_GET_VARIABLES _IOR('q', 1, query_arg_t *)

static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
        query_arg_t q;

        switch (cmd)
        {
                case QUERY_GET_VARIABLES:
                        q.status = status;
                        q.dignity = dignity;
                        q.ego = ego;
                        if (copy_to_user((query_arg_t *)arg, &q, sizeof(query_arg_t)))
                        {
                                return -EACCES;
                        }
                        break;
                default:
                        return -EINVAL;
        }

    return 0;
}

请注意,需要query_arg_t *类型,因此应用了强制转换:(query_arg_t *)arg

但是,这是否违反了严格的别名规则,因为arg类型为unsigned long而强制转换为query_arg_t *

4

4 回答 4

3

指针是整数类型。将指针存储在任何大到足以包含它的整数类型中是完全可以的。例如,以下是完全有效的 C:

double f()
{
    double a = 10.5;

    uintptr_t p = (uintptr_t)(&a);

    double * q = (double *)p;

    return *q;
}

相比之下,以下是明显的混叠违规:

short buf[100] = {};

double x = *(double*)(buf + 13);

关键是如何存储指针值并不重要。重要的是,您必须仅将这些指针视为指向对象的指针,而这些指针实际上是指向正确类型对象的指针。

在第一个示例中,p确实存储了指向 double 的指针,尽管它本身不是 a double *。在第二个示例中,buf + 13is 根本不是指向双精度的指针,因此将其取消引用是类型双关语和别名违规。

(指针和强制转换是 C 语言不是安全语言的原因之一:操作的正确性可能取决于变量的,而不仅仅是它的类型。)

于 2013-04-16T20:50:27.090 回答
2

这不会破坏别名规则。

对象arg只能在my_ioctl函数中访问:

(query_arg_t *)arg

并且对象只能通过它的 type 来访问unsigned long。强制转换仅将对象的值从unsigned long值转换为query_arg_t *值。

于 2013-04-16T19:38:24.447 回答
2

请记住,虽然这些是别名,但它们存在于完全不同的执行上下文中——用户空间和内核。严格的别名规则旨在防止编译器在代码单元中处理可能的别名时可能出现的问题。用户空间和内核空间代码永远不会发生这种情况。它们是独立的代码单元,永远不会以可能导致与严格别名规则旨在解决的问题相关的方式混合在一起。

于 2013-04-16T19:56:43.257 回答
1

unsigned long(或其他与强制转换兼容的整数类型)是所有 Linux ABI 中unsigned long的基础类型。uintptr_t

$ grep -rw uintptr_t /usr/include/stdint.h
typedef unsigned long int uintptr_t;

C99 表示任何指针类型都可以转换为uintptr_t(或其基础类型)并转换回原始指针类型,而不会丢失信息或违反严格别名规则。因此,只要调用的用户空间代码将a作为参数ioctl(fd, QUERY_GET_ARGS, ptr)传递,整个程序就符合要求。query_arg_t *ptr

另请注意,ioctl您展示的原型是内核中的驱动程序端接口。在用户空间,原型是

extern int ioctl(int fd, unsigned long int request, void *arg);

这使得第三个参数是一些具体但未指定的指针类型更加明显,并且调用者和(最终)被调用者最好就指针的实际类型达成一致。(这是 C 中的正常使用模式void *。)

(对于学究的进一步说明:实际的用户空间原型是

extern int ioctl(int fd, unsigned long int request, ...);

对于将数字常量作为第三个参数而不进行强制转换的程序来说,这是一个兼容性问题。您可能开始理解为什么ioctl不再被视为设计良好的 API。)

于 2013-04-16T19:40:59.717 回答