Linux相关函数

£可爱£侵袭症+ 提交于 2019-11-30 04:27:54

目录

内核、用户数据拷贝

module_platform_driver()

驱动获得进程的信息

DEVICE_ATTR

EXPORT_SYMBOL()

container_of

insmod给驱动传参数

其他函数


内核、用户数据拷贝

常见的有四个函数:
copy_from_user、copy_to_user、get_user(或__get_user)、put_user(或__put_user)

copy_from_user:
    原型:static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
    第一个参数to:       内核空间的数据目标地址指针
    第二个参数from:   用户空间的数据源地址指针     (void __user *也可)
    第三个参数n:         数据的长度,以字节为单位。
    如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
    例:
    static struct file_operations ker_rw_ops = {
        .owner            = THIS_MODULE,
        .unlocked_ioctl   = ker_rw_ioctl,
    };
    static long ker_rw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
            ...
            unsigned int buf[2];
            copy_from_user(buf, (const void __user *)arg, 8)
            ...
    }

copy_to_user:
    原型:static inline long copy_to_user(void __user *to,  const void *from, unsigned long n)
    第一个参数to:       用户空间的数据目标地址指针
    第二个参数from:   内核空间的数据源地址指针      (void *也可)
    第三个参数n:         数据的长度,以字节为单位。
    如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
    例:
    1.应用程序:
           #define KEY_VAL 100
           unsigned char key_vals[4];
           fd = open("/dev/buttons", O_RDWR);
           ioctl(fd, KEY_VAL, key_vals);   //第二个参数可为0或任意int型,可在驱动程序中对此值用switch判断
       驱动程序:
            static int second_drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
            {
                    unsigned char key_vals[4];
                    copy_to_user((void __user *)(arg), key_vals, 4);
               }
        2.应用程序:
           unsigned int buf[2];
           #define KER_RW_R8      100
           ioctl(fd, KER_RW_R8, buf);
           驱动程序:
           static long ker_rw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
           {
               ...
               unsigned int val;
               copy_to_user((void __user *)(arg+4), &val, 4);
                   ...
           }
          详细见:第30课第3节_驱动调试之自制工具_寄存器编辑器  (26th_debug_regeditor)

get_user(或__get_user):
    原型:#define get_user(x, p)  __get_user(x,p)
    第一个参数 x:  内核变量
    第二个参数 p:  用户空间的地址
    例:static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
         {
             void __user *argp = (void __user *)arg;
             int __user *p = argp;
             int x=3;
             get_user(x, p);
         }

put_user(或__put_user):
    原型:#define put_user(x, p)  __put_user(x,p)
    第一个参数 x:  内核变量
    第二个参数 p:  用户空间的地址
    例:static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
         {
             void __user *argp = (void __user *)arg;
             int __user *p = argp;
             int x;
             put_user(x, p);
         }

module_platform_driver()

在include/linux/platform_device.h中定义:

#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
        return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
        __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

用法:
例:定义名为first_drv的结构体的初始化、退出函数:
static struct platform_driver first_drv = {
        .probe  = ... ,
        .remove = ... ,
        .driver = {
                .name = ... ,
                .owner = THIS_MODULE ,
        },
};
module_platform_driver(first_drv);

驱动获得进程的信息

current是 一个指向当前进程的task_struct的指针。
pid:      current->pid
进程名: current->common
CPU号: smp_processor_id()
是否在中断中: in_irq()、in_interrupe()

在内核中, 进程用task_struct结构表示, 其中有char comm[TASK_COMM_LEN]成员, 其含义是:进程的名字(不包含路径)

按照标准做法, 应该使用get_task_comm()/set_task_comm()函数来获取/设置此成员(为避免竞争, 这俩函数会调用task_lock()先拿锁).

DEVICE_ATTR

        使用DEVICE_ATTR,可以在sysfs中添加“文件”,通过修改该文件内容,可在运行过程中动态控制device。
        类似的还有DRIVER_ATTR,BUS_ATTR,CLASS_ATTR。DEVICE_ATTR对应的文件在/sys/devices/目录中对应的device下面(有可能在子目录之下),而其他几个分别在driver,bus,class中对应的目录下。
        这次主要介绍DEVICE_ATTR,其他几个类似。

documentation/driver-model/Device.txt中有对DEVICE_ATTR的详细介绍。
DEVICE_ATTR的原型:
DEVICE_ATTR(_name, _mode, _show, _store)
_name:名称,也就是将在sys fs中生成的文件名称。
_mode:上述文件的访问权限,与普通文件相同,UGO的格式。
_show:显示函数,cat该文件时,此函数被调用
_store:写函数,echo内容到该文件时,此函数被调用

看看我们怎么填充这些要素:
名称可以随便起一个,便于记忆,并能体现其功能即可。
模式可以为只读0444,只写0222,或者读写都行的0666。当然也可以对User\Group\Other进行区别。
显示和写入函数就需要实现了。

实例:
第二期视频:“第32课第1.2节_3.4.2内核下的I2C驱动之框架编写代码”
insmod at24cxx_drv.ko; //只加载drv
echo at24c08 0x50 > /sys/devices/platform/s3c2440-i2c/i2c-0/new_device;
发现运行了probe
echo 0x50 > /sys/devices/platform/s3c2440-i2c/i2c-0/delete_device;
发现运行了remove函数

搜索“new_device”
i2c_core.c: static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
i2c_sysfs_new_device
    i2c_new_device(adap, &info)

EXPORT_SYMBOL()

见:Linux驱动编程中EXPORT_SYMBOL()介绍

container_of

作用

通过一个结构变量中一个成员的地址找到这个结构体变量的首地址

示例
1.定义

linux3.4.2/include/linux/kernel.h:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

linux-4.19-rc/include/linux/kernel.h:
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
__same_type的含义:如果type_a与 type_b相同的话,就会返回1,否则的话,返回0。
__builtin_types_compatible_p是GCC的内建函数。

2. 使用
linux-3.4.2/include/linux/i2c.h 
#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)

struct i2c_adapter {
struct device dev;
    ...
}

i2c-dev.c
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
                ruct i2c_adapter *adap;
                ap = to_i2c_adapter(dev);
                .
}

3.函数调用顺序
to_i2c_adapter(d);
        container_of(d, struct i2c_adapter, dev)
                const typeof( ((struct i2c_adapter *)0)->dev ) *__mptr = (d); \
                        (struct i2c_adapter *)( (char *)__mptr - offsetof(struct i2c_adapter,dev) );
                        const typeof( ((struct i2c_adapter *)0)->dev ) *__mptr = (d); \
                        (struct i2c_adapter *)( (char *)__mptr - ((size_t) &((struct i2c_adapter *)0)->dev) );

相关知识

1. typeof:获得变量的类型
typeof的参数可以是两种形式:表达式或类型。
例1:struct device dev;
        typeof dev 等价于 struct device
例2:typeof(int *) a,b; 
        等价于 int *a, *b;
例3:int func1(void);
        typeof(func1(void)) var 等价于 int var

typeof构造中的类型名不能包含存储类说明符,如extern或static;允许包含类型限定符,如const或volatile.
例1:typeof(extern int) a; //这是无效的。因为它在typeof构造中声明了extern;
        extern typeof(int)a;    //这是有效的。
例2:typeof(char*const) p; //这是有效的。常指针,不能修改指针指向;

2.为什么container_of(linux-3.4.2版本)要定义同一地址的变量呢?
const typeof( ((struct i2c_adapter *)0)->dev ) *__mptr = (d);
答:如果开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning,
但是如果去掉该行,那个就没有了,而这个警告恰恰是必须的(防止出错有不知道错误在哪里)。

3.为什么将0设为地址不会出错(即offsetof的宏定义)?
按理,0地址也就是空指针,空指针指向成员变量一定会出错,但是,此处不一样,解答如下。
本处,用了&符号(取地址),导致没有访问内存位置,只是取了地址,这些地址一般存储在
机器寄存器或是通常的本地堆栈。

例如:
例程1

int main()
{
        t *a = NULL;
        intf("a = %u\n",&(*a));
}
运行结果:
a = 0

例程2:
int main()
{
             *a = NULL;
            ntf("*a = %u\n",*a);
}
运行结果:
Segmentation fault (core dumped)

4.linux-4.19-rc与linux-3.4.2的函数差别
(1)用__same_type宏来做类型检测,显得更加的直观明了,错了还可以有提示
(2)用void *取代了char *来做减法

insmod给驱动传参数

简介
一般用户态传递参数是通过main函数,第一个参数表示args个数,即argc,第二个参数表示具体的参数。

在kernel态,无法通过这样的方式传递参数,一般使用module_param的方式,步骤如下:
1.使用module_param指定模块的参数
2.加载driver时给模块传递参数

代码
static int cnt = 61;
static char *str = "China" ;

static int word_count_init(void)
{
    printk(KERN_ALERT "word_count_init_sucess \n");
    printk(KERN_ALERT "cnt=%d , str=%s \n",cnt,str);
    return 0;
}

static void word_count_exit(void)
{
    printk(KERN_ALERT "word_count_init_exit_sucess \n");
}

//注册初始化linux驱动的函数
module_init(word_count_init);
//注册退出linux驱动的函数
module_exit(word_count_exit);
module_param(cnt,int,0644);
module_param(str,charp,0644);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mstar jacky.zhu");

试验
1.无参数执行结果
# insmod hello_world.ko 
[ 122.774769] word_count_init_sucess 
[ 122.778314] cnt=61 , str=China 
# rmmod hello_world.ko 
[ 129.133626] word_count_init_exit_sucess 
以上是不带参数,正常的挂载和卸载

2.有参数执行结果
#insmod hello_world.ko cnt=50 str='qingdao' 
[ 293.104785] word_count_init_sucess 
[ 293.108306] cnt=50 , str=qingdao 
在控制台可以将参数传递给hello_world.ko

3.sys路径结果
# ls -al /sys/module/hello_world/parameters/ 
-rw-r--r-- root root 4096 2007-01-01 20:07 str
-rw-r--r-- root root 4096 2007-01-01 20:07 cnt
在串口可以查看参数的值

# cat cnt 
50
#cd /sys/module/hello_world/parameters
#cat str
qingdao

4.module_param三个参数的解释
module_param(cnt,int,0644);
第一个参数是参数的name,自己定义
第二个参数是变量的类型,比如int,long,char,float等
第三个参数是权限,类似于文件的权限。这里应该是指哪些user可以修改这个参数的意思。
比如上面的数据:
# ls -al /sys/module/hello_world/parameters/ 
-rw-r--r-- root root 4096 2007-01-01 20:07 str
-rw-r--r-- root root 4096 2007-01-01 20:07 cnt
权限就是644,是在这里指定的。

其他函数

ARRAY_SIZE
    获得结构体数组的成员个数
    #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!