强制转换ioctl参数会破坏严格的别名规则吗?

时间:2013-04-16 19:20:55

标签: c linux linux-kernel c99 strict-aliasing

我正在使用以下ioctl原型运行 Linux 3.2 内核:

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 个答案:

答案 0 :(得分: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,尽管它本身不是double *。在第二个示例中,buf + 13根本不是指向double的指针,因此解除引用它就是类型惩罚和别名冲突。

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

答案 1 :(得分:2)

这不会破坏别名规则。

仅在arg函数

中访问对象my_ioctl
(query_arg_t *)arg

只能通过类型unsigned long访问对象。演员表仅将对象的值从unsigned long值转换为query_arg_t *值。

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

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

C99表示任何指针类型都可以转换为uintptr_t(或其基础类型)并返回到原始指针类型,而不会丢失信息或违反严格别名规则。因此,只要调用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。)