最近在公司没什么事做,突然有来写写日志和博客,这种突发的灵感来自于我在学习内核驱动代码的时候发现了结构体的一种古老的初始化方法,多亏了尚观广州校区的叶老师在群里用心的给我文字讲解,让我明白了原来结构体初始化也可以用:冒号这样的方法。
其实在C语言中,冒号:的用法有很多,比如三目运算符 表达式?表达式1:表达式2 ;
它的意思就是如果表达式成立,那么执行表达式1,否则执行表达式2 。还有一种就是广泛用于结构体的位段中,例如:
Struct node
{
Int id : 2 ;
double : 4 ;
};
大概意思就是id这个结构体成员只占int型4个字节中的2个字节,这种用法在封包的时候用得最多,比如TCP,UDP,ARP,IP,HTTP等等的协议封包的时候,包有3个字节的,有5个字节的,可惜变量的大小只有1 ,2 ,4, 8 。。。那解决这种方法的手段就是位段了,一个整形变量原来是占4个字节,现在我只要2个字节,那我就可以用上面这种方法去取值。
好了,今天的重点不在这,我想聊聊关于函数指针在结构体中的用法,因为它太重要了,在linux内核驱动开发中,很多框架就是对结构体中的函数指针,也就是实现那个函数,然后就对结构体中的成员赋值,也就是赋值一个函数的地址给结构体中的成员。废话不多说,我们直接上代码,比较一下,看看有什么不同。
#include
#include
//定义一个字符串-我名字
#define Name "yangyuanxin"
char *p = Name;
int add(int x , int y) ;
char ch(char a , char b) ;
struct node {
int id ;
char name[20];
float math_exam ;
double path ;
//在结构体中定义一个函数指针,俗称回调函数
int (*func)(int , int);
//在结构体中定义一个函数指针,俗称回调函数
char (*func1)(char , char);
};
struct node stu = {
.id = 803 , //初始化id的值为803
.name = Name , //初始化名字
.math_exam = 95.2f , //初始化成绩
path:100.3f, //注意咯,这种初始化也是可以的!
.func = add, //初始化函数add , 也就是让结构体中的函数指针指向add这个函数
.func1 = ch , //初始化函数ch也就是让结构体中的函数指针指向ch这个函数
};
int main(void)
{
//下面就不用我说了,自己分析
printf("stu.id = %d\n",stu.id);
printf("stu.name = %s\n",stu.name);
printf("stu.math_exam = %.2f\n",stu.math_exam);
printf("stu.path = %.3lf\n",stu.path);
printf("stu.func = %d\n",stu.func(1 , 2));
printf("stu.func1 = %c\n",stu.func1('a' , 'b'));
return 0 ;
}
//函数add , 传参数实现x和y相加
int add(int x , int y)
{
return x+y ;
}
//如果a > b , 那么就返回a这个字符
char ch(char a , char b)
{
if(a > b)
return a ;
}
运行结果:
看完了注释和代码,大概就明白了函数指针在结构体中的运用了吧?其实就是将函数的地址返回给函数地址,因为我们知道函数名其实就是函数的首地址,所以是不是很简单?不过这只是简单的举个例子噢!来看看内核驱动是如何定义的,咱们还是直接上代码:
以下是我通过内核驱动去修改的一个字符设备驱动,主要实现PWM蜂鸣器的功能,使用的平台是基于ARM-ContexA9-tiny4412这款开发板,大家有兴趣的话也可以去买一块来玩玩。嘿嘿!!
//下面的头文件是与linux内核相关的一些头文件,看看就好,主要看我下面的具体分//析,这样便于快速上手linux内核的设备驱动。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//定义设备的名字为 pwm
#define DEVICE_NAME "pwm"
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
#define NS_IN_1HZ (1000000000UL)
//蜂鸣器PWM_ID 0
#define BUZZER_PWM_ID 0
//蜂鸣器GPIO配置
#define BUZZER_PMW_GPIO EXYNOS4_GPD0(0)
//定义一个结构体指针
static struct pwm_device *pwm4buzzer;
//定义一个结构体信号量指针,因为信号量与锁的机制差不多
//Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第//一个。一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被//并行的运行。
//Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就//要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限//制对于某一资源的同时访问。
static struct semaphore lock;
static void pwm_set_freq(unsigned long freq) {
//PWM的占空比的配置
int period_ns = NS_IN_1HZ / freq;
pwm_config(pwm4buzzer, period_ns / 2, period_ns);
pwm_enable(pwm4buzzer);
//配置相应的GPIO
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));
}
//stop方法函数,来源于operations结构体
static void pwm_stop(void) {
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);
pwm_config(pwm4buzzer, 0, NS_IN_1HZ / 100);
pwm_disable(pwm4buzzer);
}
//open方法函数,来源于operations结构体,主要打开pwm的操作
static int tiny4412_pwm_open(struct inode *inode, struct file *file) {
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}
//close方法函数,来源于operations结构体,主要是关闭pwm操作
static int tiny4412_pwm_close(struct inode *inode, struct file *file) {
up(&lock);
return 0;
}
//控制io口方法函数,来源于operations结构体,其实就是上层系统调用传入一条命令,//驱动识别命令,然后执行相应过程。
static long tiny4412_pwm_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case PWM_IOCTL_SET_FREQ:
if (arg == 0)
return -EINVAL;
pwm_set_freq(arg);
break;
case PWM_IOCTL_STOP:
default:
pwm_stop();
break;
}
return 0;
}
//这就是我们要看的结构体了,其实这个结构体的定义在另一个.h当中,看看它的初始//化方式,跟我们上面那个程序的分析基本上是一样的。对应的函数名(也就是函数的//首地址)赋值给对应的结构体成员,实现了整个结构体的初始化,这样的方法类似于//C++和JAVA等高级语言的操作。
static struct file_operations tiny4412_pwm_ops = {
.owner = THIS_MODULE, //表示本模块拥有
.open = tiny4412_pwm_open, //表示调用open函数
.release = tiny4412_pwm_close, //…
.unlocked_ioctl = tiny4412_pwm_ioctl,
};
//杂类设备的注册
static struct miscdevice tiny4412_misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &tiny4412_pwm_ops,
};
//pwm设备初始化,设备在被insmod插入模块到内核的过程中会调用这个函数
static int __init tiny4412_pwm_dev_init(void) {
int ret;
ret = gpio_request(BUZZER_PMW_GPIO, DEVICE_NAME);
if (ret) {
printk("request GPIO %d for pwm failed\n", BUZZER_PMW_GPIO);
return ret;
}
gpio_set_value(BUZZER_PMW_GPIO, 0);
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);
pwm4buzzer = pwm_request(BUZZER_PWM_ID, DEVICE_NAME);
if (IS_ERR(pwm4buzzer)) {
printk("request pwm %d for %s failed\n", BUZZER_PWM_ID, DEVICE_NAME);
return -ENODEV;
}
pwm_stop();
sema_init(&lock, 1);
ret = misc_register(&tiny4412_misc_dev);
printk(DEVICE_NAME "\tinitialized\n");
return ret;
}
//设备在被卸载rmmod的过程中会调用这个函数
static void __exit tiny4412_pwm_dev_exit(void) {
pwm_stop();
misc_deregister(&tiny4412_misc_dev);
gpio_free(BUZZER_PMW_GPIO);
}
//模块初始化
module_init(tiny4412_pwm_dev_init);
//销毁模块
module_exit(tiny4412_pwm_dev_exit);
//声明GPL协议
MODULE_LICENSE("GPL");
//作者:yangyuanxin
MODULE_AUTHOR("Yangyuanxin");
//描述:三星PWM设备
MODULE_DESCRIPTION("Exynos4 PWM Driver");
好了,代码我们分析完了,这就只是实现一个简简单单的字符设备PWM蜂鸣器,然而,内核中还有很多更为复杂的结构体中的函数指针,要多看多练,慢慢就熟了。我们再来回忆一下内核的结构体初始化是怎样的:
static struct file_operations tiny4412_pwm_ops = {
.owner = THIS_MODULE, //表示本模块拥有
.open = tiny4412_pwm_open, //表示调用open函数
.release = tiny4412_pwm_close, //…
.unlocked_ioctl = tiny4412_pwm_ioctl,
};
上面这个代码初始化了PWM相关的操作函数,而这些函数以函数指针的形式定义在一个结构体里,类似的方法就和我举的第一个例子一样。
struct node stu = {
.id = 803 , //初始化id的值为803
.name = Name , //初始化名字
.math_exam = 95.2f , //初始化成绩
path:100.3f, //注意咯,这种初始化也是可以的!
.func = add, //初始化函数add , 也就是让结构体中的函数指针指向add这个函数
.func1 = ch , //初始化函数ch也就是让结构体中的函数指针指向ch这个函数
};
其实结构体中函数指针就是这么回事,用途很大,在内核设备驱动的开发中,如果能够用好函数指针,那么将对开发大大的有利!今天就到这里,往后遇到问题,我还会给大家一起分享。
我的CSDN博客: http://blog.csdn.net/morixinguan/article/month/2015/12