make menuconfig过程解析
作者 codercjg 在 28 九月 2015, 5:27 下午
make menuconfig用于图形界面配置linux内核。
linux源码根目录下有.config文件,其他子目录下一般都会有一个Kconfig文件和Makefile文件。
三个文件的作用分别为:
Kconfig: 定义配置项
.config: 设置配置项的值(可通过make menuconfig界面配置或者直接修改)
Makefile:读取配置项的值,并根据该值决定是否编译该目录下该配置项关联的c源文件
make menuconfig 首先会读取/arch/arm/Kconfig, 然后根据该Kconfig文件引用其他目录下的Kconfig,
最后和根目录下的.config对比,生成图形配置界面,配置完成后的结果保存在.config文件中。
比如当写一个hello的字符驱动hello.c后,放在linux源码/driver/char/目录下后,要一起编译进内核需要
修改该目录下的Kconfig和Makefile.
在/driver/char/Kconfig文件里加入以下内容:
config HELLO
bool “/dev/hello hello driver example ”
default y
help
Say Y here if you want to support /dev/hello device.
然后make menuconfig后就可以看到如下内容:
但这时执行make zImage还不能编译成功,要正确编译还需要在/driver/char/Makefile文件里加上:
obj-$(CONFIG_HELLO)+= hello.o
当在make menuconfig图形界面里选中了hello选项后,生成的.config文件会有CONFIG_HELLO=y
当执行make zImage命令时,obj-y += hello.o 就表示要把该目标文件编译进内核。
如果是模块,则可把HELLO配置项设为tristate类型,在make menuconfig图形界面选择hello M模块选项时
生成的.config文件会CONFIG_HELLO=m
当执行make modules命令时,obj-m +=hell.o就表示把该目标文件编译为模块。
obj-y和obj-m是linux 编译时定义的全局变量,分别表示要编译进内核的文件列表和编译为模块的文件列表。
其他相关命令:
make clean 清除编译内核生成的相关文件
make distclean 清除编译内核生成的相关文件,并且删除根目录下的.config文件
make xxxx_defconfig 会根据/arch/arm/configs/目录下的xxx_defconfig配置文件生成根目录下的.config文件。
分类: Linux驱动 | 评论
platform总线设备驱动模型
作者 codercjg 在 28 九月 2015, 3:57 下午
linux设备驱动模型中分总线、设备、驱动三个重要概念。
每种设备都要挂在一种总线上,比如MCU外部引脚引出的i2c、spi等,然而在MCU内部的i2c控制器、spi控制器等却不在任何总线上。
因此,linux抽象出platform总线的概念,MCU内部的各种设备都可挂在platform总线上。
platform设备驱动的步骤:
platform_device_register()=>platform_driver_register()=>platform_driver_unregister()=>platform_device_unregister()
platform设备驱动分为两个:一个平台设备platform_device和一个平台驱动platform_driver。
下面是一个最简单的platform hello驱动
hello-device.c源码
include
include
include
void hello_device_release(struct device *dev)
{
printk(“hello_device_release\n”);
}
struct platform_device hello_device = {
.name = “hello”,
.id = -1,
.dev = {
.release = hello_device_release,
},
};
static int __init hello_device_init(void)
{
int ret;
printk(“hello_device_init\n”);
ret = platform_device_register(&hello_device);
if(ret){
printk(“platform_device_register failed\n”);
}
return ret;
}
static void __exit hello_device_exit(void)
{
platform_device_unregister(&hello_device);
printk(“hello_device_exit\n”);
}
MODULE_AUTHOR(“codercjg”);
MODULE_LICENSE(“Dual BSD/GPL”);
module_init(hello_device_init);
module_exit(hello_device_exit);
hello-driver.c源码
include
include
include
int hello_probe(struct platform_device *pdev)
{
printk(“hello_probe\n”);
return 0;
}
int hello_remove(struct platform_device *pdev)
{
printk(“hello_remove\n”);
return 0;
}
struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.driver = {
.name = “hello”,
},
};
static int __init hello_driver_init(void)
{
int ret;
printk(“hello_driver_init\n”);
ret = platform_driver_register(&hello_driver);
if(ret){
printk(“hello_driver_init failed\n”);
}
return ret;
}
static void __exit hello_driver_exit(void)
{
platform_driver_unregister(&hello_driver);
printk(“hello_driver_exit\n”);
}
MODULE_AUTHOR(“codercjg”);
MODULE_LICENSE(“Dual BSD/GPL”);
module_init(hello_driver_init);
module_exit(hello_driver_exit);
Makefile源码
obj-m := hello-device.o hello-driver.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
.PHONY: clean
clean:
rm -rf *.o *.ko
编译后执行命令:
sudo inmod ./hello-device.ko
sudo inmod ./hello-driver.ko
hello-device和hello-driver通过.name = “hello” 匹配
之后可在 /sys/bus/platform/devices/和 /sys/devices/platform/下找到hello
分类: Linux驱动 | 评论
序列文件seq_file
作者 codercjg 在 25 九月 2015, 5:16 下午
seq_file可在/proc下创建一个文件用于表示内核中的数据,用户可以用cat命令打印其内容。
实现proc文件需要一个向该文件填充内核数据的迭代器seq_operations和用户读写该文件的接口file_operations。
seq_operations中包含seq_start、seq_show、seq_next、seq_stop等迭代器相关函数。
file_operations中包含seq_open、seq_read、seq_lseek、seq_release等proc文件操作相关函数,
这些函数在内核源码/fs/seq_file中有默认实现,通常只需改写seq_open。
1.源码:
include
include
include
include
static char* strs[] = {“aa”, “bb”, “c”};
/* 迭代开始 /
static void seq_start(struct seq_file m, loff_t pos)
{
printk(“seq_start\n”);
if(pos >= 3){
return NULL;
}
else {
pos = 0;
return strs[pos];
}
}
/ 迭代下一个 /
static void seq_next(struct seq_file m, void v, loff_t pos)
{
printk(“seq_next\n”);
if(++pos >= 3){
return NULL;
}
else{
return strs[pos];
}
}
/ 迭代结束 */
static void seq_stop(struct seq_file *m, void v)
{
printk(“seq_stop\n”);
}
/ 写当前元素到proc文件 */
static int seq_show(struct seq_file m, void v)
{
printk(“seq_show\n”);
seq_printf(m, “%s”, (char)v);
return 0;
}
/ 迭代器 /
static struct seq_operations seqops = {
.start = seq_start,
.next = seq_next,
.stop = seq_stop,
.show = seq_show,
};
/ 打开proc文件 */
int my_seq_open(struct inode inode, struct file file)
{
printk(“seq_open\n”);
return seq_open(file, &seqops);
}
/ proc文件操作接口 /
static struct file_operations fileops = {
.open = my_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init myproc_init(void)
{
struct proc_dir_entry entry;
entry = proc_create(“myproc”, 777, NULL, &fileops); / 创建/proc/myproc文件 /
if(!entry){
printk(“create /proc/myproc error\n”);
remove_proc_entry(“myproc”, NULL);
return -1;
}
printk(“create /proc/myproc ok\n”);
return 0;
}
static void __exit myproc_exit(void)
{
remove_proc_entry(“myproc”, NULL); / 删除/proc/myproc文件 */
printk(“remove /proc/myproc ok\n”);
}
MODULE_AUTHOR(“codercjg”);
MODULE_LICENSE(“Dual BSD/GPL”);
module_init(myproc_init);
module_exit(myproc_exit);
2.查看结果
sudo inmod seqfile.ko
sudo cat/proc/myproc
会打印出aabbcc
3.查看日志
tail -f /var/log/syslog查看日志:
Sep 25 10:41:48 instant-contiki kernel: [ 539.889249] create /proc/myproc ok
Sep 25 10:42:05 instant-contiki kernel: [ 556.951100] seq_open
Sep 25 10:42:05 instant-contiki kernel: [ 556.951130] seq_start
Sep 25 10:42:05 instant-contiki kernel: [ 556.951131] seq_show
Sep 25 10:42:05 instant-contiki kernel: [ 556.951133] seq_next
Sep 25 10:42:05 instant-contiki kernel: [ 556.951133] seq_show
Sep 25 10:42:05 instant-contiki kernel: [ 556.951134] seq_next
Sep 25 10:42:05 instant-contiki kernel: [ 556.951135] seq_show
Sep 25 10:42:05 instant-contiki kernel: [ 556.951136] seq_next
Sep 25 10:42:05 instant-contiki kernel: [ 556.951137] seq_stop
Sep 25 10:42:05 instant-contiki kernel: [ 556.951249] seq_start
Sep 25 10:42:05 instant-contiki kernel: [ 556.951250] seq_stop
Sep 25 10:43:11 instant-contiki kernel: [ 622.739478] remove /proc/myproc ok
4.总结:
由源码和日志可知写入myproc文件是通过迭代器用seq_printf()一个元素一个元素的写入,。
首先调用seq_start()获得第一个元素,然后调用seq_printf()写入myproc文件,
然后调用seq_next()获取下一个元素,接着调用seq_printf(),直到seq_next()返回NULL后调用seq_stop()迭代结束。
即seq_start()->seq_show()->seq_next()->seq_show()->…->seq_next()->seq_stop();
sodu rmmod seqfile后,/proc/myproc文件消失了。
分类: Linux驱动 | 评论
linux MISC设备驱动写法
作者 codercjg 在 23 九月 2015, 4:40 下午
在编写字符设备驱动过程中,驱动入口函数中常有一些alloc_chrdev_region()分配主次设备号,
device_create()创建设备节点/dev/xxx,cdev_init()和cdev_add()注册为字符驱动等共性操作。
而出口函数又需要收回设备号、删除设备节点、卸载字符驱动等。
为了简化字符设备驱动的编写,出现了MISC设备驱动,它只需要调用misc_register()和misc_deregister()
即可完成字符设备驱动的入口函数和出口函数相关操作。在linux源码drivers/char/misc.c中可找到misc的具体实现。
misc设备驱动本质仍为字符设备驱动,只是所有misc设备的主设备号为10,根据次设备号和设备节点名对具体设备进行区分。
次设备号和设备节点名可在驱动中定义的struct miscdevice变量中指定。
普通字符设备驱动:
struct class *hello_class;
int globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
if (globalfifo_major) {
ret = register_chrdev_region(devno, 1, “globalfifo”);
} else {
// 动态分配主设备号
ret = alloc_chrdev_region(&devno, 0, 1, “globalfifo”);
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
memset(&fifodev, 0, sizeof(struct globalfifo_dev));
cdev_init(&fifodev.cdev, &globalfifo_fops);
fifodev.cdev.owner = THIS_MODULE;
cdev_add(&fifodev.cdev, devno, 1);
sema_init(&fifodev.sem, 1);
init_waitqueue_head(&fifodev.r_wait);
init_waitqueue_head(&fifodev.w_wait);
// 创建设备节点/dev/byte
hello_class = class_create(THIS_MODULE, “fifo_class”);
device_create(hello_class, NULL, MKDEV(globalfifo_major, 0), NULL, “byte”);
printk(KERN_INFO”fifo_init\n”);
return 0; // 记得要有返回值,否则insmod的时候会提示出错
}
void globalfifo_exit(void)
{
// 删除设备节点/dev/byte
device_destroy(hello_class, MKDEV(globalfifo_major, 0));
class_destroy(hello_class);
cdev_del(&fifodev.cdev);
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
printk(KERN_INFO”fifo_exit\n”);
}
module_init(globalfifo_init);
module_exit(globalfifo_exit);
misc设备驱动:
static struct miscdevice misc = {
** .minor = MISC_DYNAMIC_MINOR,**
** .name = “byte”,**
** .fops = &globalfifo_fops,**
};
int globalfifo_init(void)
{
int ret;
memset(&fifodev, 0, sizeof(struct globalfifo_dev));
sema_init(&fifodev.sem, 1);
init_waitqueue_head(&fifodev.r_wait);
init_waitqueue_head(&fifodev.w_wait);
** ret = misc_register(&misc);**
printk(KERN_INFO”fifo_init\n”);
return ret; // 记得要有返回值,否则insmod的时候会提示出错
}
void globalfifo_exit(void)
{
** misc_deregister(&misc);**
}
sudo insmod ./misc_globafifo.ko后即可找到/dev/byte节点
执行命令查看生成的设备节点/dev/byte
ls /dev -al
crwxrwxrwx 1 root root 10, 57 Sep 10 06:32 byte
从打印的消息中可以看出字符设备/dev/byte主设备号为10,次设备号为57。
分类: Linux驱动 | 评论
sscanf()遇到空格
作者 codercjg 在 18 九月 2015, 3:50 下午
sscanf从字符串按某种格式提取参数遇到空格就会停止。
int a;
char * str = “hello 123″;
sscanf(str, “%s%d”, a);
%s可以忽略空格之前的hello
分类: Linux | 评论
只有一个字节的linux字符设备驱动
作者 codercjg 在 9 九月 2015, 3:48 下午
只有一个字节的字符设备驱动,实现阻塞和非阻塞读写、异步信号通知、select、ioctl等。
测试系统为ubuntu, 内核为linux3.2 。kernel2.3.36后ioctl变成unlock_ioctl,而且少了一个inode参数。
编写代码过程中,可以把kernel源码用source insight建一个工程,然后把驱动加入这个工程,这样就能有
代码提示的功能,而且查看源码也方便,再也不用因记不住函数名和参数烦扰。
测试过程中可用tail -f /etc/var/syslog 实时查看驱动的打印信息,而不用一遍遍cat /etc/var/syslog了。
可以用lsmod查看驱动是否已加载,用cat /proc/devices查看设备节点是否创建成功。
源码下载地址:http://pan.baidu.com/s/1t1Zt0
驱动源码:
include
include #include #include #include #include #include #include #include #include #include #include #include
define GLOBAL_FIFO_MAJOR 0
static int globalfifo_major = GLOBAL_FIFO_MAJOR;
/* 只有一个字节的字符设备/struct globalfifo_dev {struct cdev cdev;unsigned char mem; /一字节缓存/unsigned char full; /可写标志/struct semaphore sem;/ 信号量/wait_queue_head_t r_wait; /读进程等待队列头/wait_queue_head_t w_wait;/写进程等待队列头/struct fasync_struct async_queue; /用于读的异步结构指针/};
struct globalfifo_dev fifodev;/文件打开函数/int globalfifo_open(struct inode inode, struct file filp){/将设备结构体指针赋值给文件私有数据指针/filp->private_data = &fifodev;return 0;}
/文件释放函数/int globalfifo_release(struct inode inode, struct file filp){/ 将文件从异步通知列表中删除 /
return 0;}
/ ioctl设备控制函数 /static int globalfifo_ioctl(struct file filp, unsigned int cmd, unsigned long arg){struct globalfifo_dev dev = (struct globalfifo_dev)filp->private_data;switch(cmd){case 1:down(&dev->sem);dev->mem = 0;up(&dev->sem);printk(KERN_INFO”zero mem\n”);break;default:return -EINVAL;}
return 0;}
/ poll函数 /static unsigned int globalfifo_poll(struct file filp, poll_table wait){unsigned int mask = 0;struct globalfifo_dev dev = (struct globalfifo_dev)filp->private_data;printk(KERN_INFO”poll\n”);down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);poll_wait(filp, &dev->w_wait, wait);if (dev->full == 1){mask |= POLLIN | POLLRDNORM; /标示数据可读/}else if (dev->full == 0){mask |= POLLOUT | POLLWRNORM; /标示数据可写/}
up(&dev->sem);return mask;}
/ globalfifo fasync函数*/static int globalfifo_fasync(int fd, struct file filp, int mode){struct globalfifo_dev dev = filp->private_data;printk(KERN_INFO”fasync_helper\n”);return fasync_helper(fd, filp, mode, &dev->async_queue);}/globalfifo读函数/static ssize_t globalfifo_read(struct file *filp, char __user buf, size_t count,loff_t ppos){int ret = 1;
printk(KERN_INFO”to read %d\n”, count);struct globalfifo_dev dev = (struct globalfifo_dev)filp->private_data;DECLARE_WAITQUEUE(read, current);add_wait_queue(&dev->r_wait, &read);
down(&dev->sem);if (dev->full == 0) { // 如果不可读if (filp->f_flags & O_NONBLOCK) {// 非阻塞直接返回ret = -EAGAIN;printk(KERN_INFO”read EAGAIN\n”);goto out;} else {__set_current_state(TASK_INTERRUPTIBLE);while (dev->full == 0) {up(&dev->sem);schedule();if (signal_pending(current)) { // 阻塞时被信号打断ret = -ERESTARTSYS;printk(KERN_INFO”read ERESTARTSYS\n”);goto out;}down(&dev->sem);}// 使用循环原因:可能有多个读进程被唤醒,进行读操作前可能已被其他读进程抢先//所以需要重新判断可读条件}
}// 读操作if (copy_to_user(buf, &dev->mem, 1)) {ret = -EFAULT;printk(KERN_INFO”read EFAULT\n”);goto out;}
dev->full = 0;up(&dev->sem);printk(KERN_INFO”wake_up w\n”);wake_up_interruptible(&dev->w_wait); // 唤醒写等待队列中的进程ret = 1;
out:remove_wait_queue(&dev->r_wait, &read);__set_current_state(TASK_RUNNING);printk(KERN_INFO”read end\n”);
return ret;}/globalfifo写操作/static ssize_t globalfifo_write(struct file *filp, const char __user buf,size_t count, loff_t ppos){int ret = 1;
printk(KERN_INFO”to write %d\n”, count);struct globalfifo_dev dev = (struct globalfifo_dev)filp->private_data;DECLARE_WAITQUEUE(write, current);add_wait_queue(&dev->w_wait, &write);
down(&dev->sem);if (dev->full == 1) { //如果不可写if (filp->f_flags & O_NONBLOCK) { // 非阻塞直接返回printk(KERN_INFO”write: O_NONBLOCK\n”);ret = -EAGAIN;goto out;} else {__set_current_state(TASK_INTERRUPTIBLE);while (dev->full == 1) {up(&dev->sem);schedule(); // 阻塞进程if (signal_pending(current)) { // 阻塞时被信号打断ret = -ERESTARTSYS;printk(KERN_INFO”write: ERESTARTSYS\n”);goto out;}down(&dev->sem);}// 使用循环原因:可能有多个写进程被唤醒,进行写操作前可能已被其他写进程抢先//所以需要重新判断可写条件}
}// 写操作if (copy_from_user(&dev->mem, buf, 1) ) {ret = -EFAULT;printk(KERN_INFO”write: EFAULT\n”);goto out;}
dev->full = 1;up(&dev->sem);printk(KERN_INFO”wake_up r\n”);// 唤醒读等待队列中的进程wake_up_interruptible(&dev->r_wait);ret = 1;// 发送异步信号给读进程if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
out:remove_wait_queue(&dev->w_wait, &write);__set_current_state(TASK_RUNNING);printk(KERN_INFO”write end\n”);
return ret;
}
/文件操作结构体/static const struct file_operations globalfifo_fops = {.owner = THIS_MODULE,.read = globalfifo_read,.write = globalfifo_write,.unlocked_ioctl = globalfifo_ioctl,.poll = globalfifo_poll,.open = globalfifo_open,.release = globalfifo_release,.fasync = globalfifo_fasync,};struct class *hello_class;int globalfifo_init(void){int ret;dev_t devno = MKDEV(globalfifo_major, 0);if (globalfifo_major) {ret = register_chrdev_region(devno, 1, “globalfifo”);} else {// 动态分配主设备号ret = alloc_chrdev_region(&devno, 0, 1, “globalfifo”);globalfifo_major = MAJOR(devno);}if (ret < 0)return ret;memset(&fifodev, 0, sizeof(struct globalfifo_dev));
cdev_init(&fifodev.cdev, &globalfifo_fops);fifodev.cdev.owner = THIS_MODULE;cdev_add(&fifodev.cdev, devno, 1);sema_init(&fifodev.sem, 1);init_waitqueue_head(&fifodev.r_wait);init_waitqueue_head(&fifodev.w_wait);
// 创建设备节点/dev/bytehello_class = class_create(THIS_MODULE, “fifo_class”);device_create(hello_class, NULL, MKDEV(globalfifo_major, 0), NULL, “byte”);printk(KERN_INFO”fifo_init\n”);return 0; // 记得要有返回值,否则insmod的时候会提示出错}
void globalfifo_exit(void){
// 删除设备节点/dev/bytedevice_destroy(hello_class, MKDEV(globalfifo_major, 0));class_destroy(hello_class);
cdev_del(&fifodev.cdev);unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);printk(KERN_INFO”fifo_exit\n”);}
MODULE_AUTHOR(“codercjg”);MODULE_LICENSE(“Dual BSD/GPL”);
module_param(globalfifo_major, int, S_IRUGO);
module_init(globalfifo_init);module_exit(globalfifo_exit);
编译驱动Makefile:
obj-m := globalfifo.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
.PHONY: clean
clean:
rm -rf *.o *.ko
加载驱动脚本:
!/bin/bash
sudo insmod ./globalfifo.ko
sudo chmod 777 /dev/byte
卸载驱动脚本:
!/bin/bash
sudo rm /dev/byte
sudo rmmod globalfifo
阻塞写:
include
include
include
include
include
int main(int argc, char **argv)
{
int fd;
ssize_t size;
char ch;
if(argc<2){
printf(“usage: write 1\n”);
return 1;
}
ch = argv[1][0];
fd = open(“/dev/byte”, O_WRONLY);
size = write(fd, &ch, 1);
if(size > 0){
printf(“%c\n”, ch);
}else{
printf(“err\n”);
}
close(fd);
return 0;
}
阻塞读:
include
include
include
include
include
int main(void)
{
int fd;
ssize_t size;
char buf[2];
fd = open(“/dev/byte”, O_RDONLY);
size = read(fd, buf, 1);
if(size > 0){
printf(“%c\n”, buf[0]);
}else{
printf(“err\n”);
}
close(fd);
return 0;
}
异步信号读:
include
include
include
include
include
include
include
int fd;
char ch;
void io_handler(int sigio)
{
size_t size;
size = read(fd, &ch, 1);
if(size > 0){
printf(“%c\n”, ch);
}else{
printf(“err\n”);
}
}
int main(void)
{
int flag;
signal(SIGIO, io_handler);
fd = open(“/dev/byte”, O_RDONLY);
if(ioctl(fd, 1)<0){
perror(“ioctl”);
return 1;
}
fcntl(fd, F_SETOWN, getpid());
flag = fcntl(fd, F_GETFL);
flag |= FASYNC;
fcntl(fd, F_SETFL, flag);
while(ch != ‘q’);
close(fd);
return 0;
}
select读:
include
include
include
include
include
int main(void)
{
fd_set inset;
struct timeval timeout;
int ret;
int fd;
ssize_t size;
char ch;
fd = open(“/dev/byte”, O_RDONLY);
while(1)
{
timeout.tv_sec = 2;
timeout.tv_usec = 0;
FD_ZERO(&inset);
FD_SET(fd, &inset);
ret = select(fd+1, &inset, NULL, NULL, NULL);
if(ret>0 && FD_ISSET(fd, &inset))
{
read(fd, &ch, 1);
printf(“%c\n”, ch);
if(ch == ‘q’)
break;
} else if(ret==0)
{
printf(“time out\n”);
}else{
perror(“select”);
}
}
close(fd);
return 0;
}
分类: Linux驱动 | 评论
实时打印syslog信息
作者 codercjg 在 8 九月 2015, 5:20 下午
tail -f /var/log/syslog
分类: Linux | 评论