在学习驱动的时候我遇到了很多问题,所以我的学习路线是这样的:

编写驱动发现.ko文件需要放入开发板的目录中,然后就学习通过nfs创建共享文件,在配置nfs时发现网络没有连接上,所以就学习怎样配置IP地址,在传输完.ko文件后,发现自动生成不了dev nod,在经过一番寻找问题后发现是文件系统没配置好,所以我又配置了一遍mdev。最终成功!(耗时2天呀。)

正文:

一、驱动文件框架

1.1. 编写驱动文件

一个驱动文件包含以下三个部分

  • 初始化驱动:首先需要定义一个文件类(在C里相当于一个结构体),列出其需要的成员函数(空函数);然后在操作系统中注册这个文件驱动,并建立这个驱动对应的设备节点。最后修饰下初始化函数,将其通过编译成可动态加载的module。
  • 成员函数:完善初始化中加入文件类中的函数。
  • 卸载驱动:注销文件驱动,并删除这个驱动对应的设备节点。

Linux设备分成三种基本类型:

  1. 字符设备
  2. 块设备
  3. 网络设备

设备驱动程序也分为对应的三类:

  1. 字符设备驱动程序
  2. 块设备驱动程序
  3. 网络设备驱动程序

设备节点被创建在/dev下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个ID 上。 相当于硬盘的inode一样的东西,立面记录了硬件设备的位置和信息

在Linux中,所有设备都以文件的形式存放在/dev目录下,都是通过文件的方式进行访问,设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。

有很重要的一点:设备节点,驱动,硬件设备是如何关联到一起的呢?

---> 这是通过设备号实现的,包括主设备号和次设备号。当创建一个设备节点时需要指定主设备号和次设备号。应用程序通过名称通过名称访问设备,而设备号指定了对应的驱动程序和对应的设备。主设备号标识设备对应的驱动程序,次设备号由内核使用,用于确定设备节点所指设备。

其中设备类型、设备号、设备结点可以通过  ls /dev -l 查看。

接着开始细品驱动文件

1.1.1 配置驱动文件

结构体file_operation 在头文件fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针,相当于对设备进行config。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。通过查看file_operation源码可以看出存储着许多内核模块中执行对应操作的地址,在应用程序中使用其中的部分成员,对应的没有调用的成员函数则会设置为NULL。

举个栗子,若是按键驱动,每个字符设备需要定义.read用来读取设备数据的函数。另一个栗子,本文里编写LED驱动,所以需要定义.write用来写寄存器(open中初始化LED write中配置LED),对其config如下:

static struct file_operations drv_fops = {.owner  =   THIS_MODULE,    // 相当于This指针.open   =   drv_open,       // 将.open 函数指针指向drv_open 函数.write    =    drv_write,   // 将.write函数指针指向drv_write函数
};

1.1.2 注册驱动文件

在linux 2.6之后有register_chrdev_region()或者register_chrdev()函数可以注册驱动文件。register_chrdev_region()相比于register_chrdev()更加简便。这里为了方便理解以register_chrdev()来分析:

major = register_chrdev(0,     "led_drv", &drv_fops); // register chrdev. to major //(major, "drv name",  file operations);
  • major: 生成驱动文件的主设备号。若设置为0,系统将自动分配主设备号给驱动文件,并且函数的返回值为主设备号。所以在运行完这一行程序后会在系统中生成一个设备,可以通过#cat /proc/device中查询。
  • drv_name: 驱动名
  • file_operation: 第一步中创建的文件类

1.1.3 创建设备节点

①不怕麻烦版本:在系统中调用mknod命令创建设备节点,命令格式为mknod name { b | c } Major Minor ,b表示块设备,c表示字符设备。

②高端版本:搭配mdev文件系统,在初始化函数中创建设备节点,使用自动创建的前提是用户空间移植了udev。在驱动的初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。在移除模块时,要调用device_destroy 和class_destroy相应的删除自动创建的设备和类。

很简单就两步:一、定义一个类指向Step1中文件类;二、创建文件类对应的设备节点(主设备号&次设备号)

static struct class *drv_class;
static struct class_device    *drv_class_dev;
-------------------------------------------------------------------------------------------drv_class = class_create(THIS_MODULE, "leddrv");//(.owner, 类名)
if(IS_ERR(drv_class))
{printk( "ERROR creat key class");
}
//(指向文件类的类名, 父类节点,  (主设备号,次设备号),    ,设备节点名字)
drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "led");
if(drv_class_dev == NULL)
{printk("ERROR creat dev");
}

1.1.4 成员函数

Step1中调用的函数,以最简单printk为例(只进行打印操作):

static int drv_open(struct inode *inode, struct file *file)
{printk("drv_open\n");     return 0;
}
static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{printk("drv_write\n");return 0;
}

1.1.5 卸载驱动

两部分:卸载驱动&删除设备节点

1.1.6 模块化驱动文件

首先修饰init函数和exit函数,相当于告诉内核这个函数为初始化函数和退出函数

module_init(first_drv_init); //When install drv, system will find init function
module_exit(first_drv_exit);   // Uinstall drv

需要在驱动文件中加入模块许可声明(适合linux2.4&2.6)否则insmod驱动时将不能与/proc/kallsyms中的符号正常连接:

MODULE_LICENSE("GPL");

则我们的驱动框架为:

//drv_frame.c#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>static struct class *drv_class;
static struct class_device    *drv_class_dev;static int drv_open(struct inode *inode, struct file *file) //设备节点,参数项
{printk("drv_open\n");     return 0;
}static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{printk("drv_write\n");return 0;
}static struct file_operations drv_fops = {.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open   =   drv_open,     .write    =    drv_write,
};
int major;
static int drv_init(void)
{major = register_chrdev(0, "led_drv", &drv_fops); // register chrdev. to major (major, "drv name", file operations);drv_class = class_create(THIS_MODULE, "leddrv");if(IS_ERR(drv_class)){printk( "ERROR creat key class");}drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "led");if(drv_class_dev == NULL){printk("ERROR creat dev");}return 0;
}static void drv_exit(void)
{unregister_chrdev(major, "led_drv"); // 卸载class_device_unregister(drv_class_dev);class_destroy(drv_class);
}module_init(drv_init); //When install drv, system will find init function
module_exit(drv_exit);   // Uinstall drvMODULE_LICENSE("GPL");

1.2. 配置mdev文件系统

上文也提到了,构建设备节点是需要mdev协助的,mdev其实是udev的简化版,通过读取内核信息来创建设备文件,要使用mdev,需要内核支持sysfs文件系统,若想减少对flash的读写还得支持tmpfs文件系统(可有可没有,我tmpfs挂载不上),其配置方法如下:

配置开机自动运行文件/etc/init.d/rcS ,添加以下几句命令:

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernelhotplug
mdev -s

其中mount -a命令是将文件/etc/fstab中的设备进行挂载,所以这里应该添加sysfs和tmpfs文件系统的挂载:

# device       mount-point       type      options     dump      fsck order
proc           /proc             proc      defaults    0         0
tmpfs          /tmp              tmpfs     defaults    0         0
tmpfs          /dev              tmpfs     defaults    0         0
sysfs          /sys              sysfs     defaults    0         0

挂载的文件系统可以通过以下指令进行观察:

#cat /proc/mounts

1.3. 应用程序

接着就是随便编写一个应用程序(应用程序用来控制文件)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>int main(int argc, char **argv)
{int fd;int val = 1;fd = open("/dev/led", O_RDWR);//设备节点  读写打开if (fd < 0){printf("can't open!\n"); }write(fd, &val, 4);return 0;
}

由于open函数的返回值为一个int类型,当文件打开出错时会返回-1。如果为大于0的值,那么这个值代表的就是文件描述符。所以有更一般的写法:

if((fd=open("/dev/ttys0",O_RDWR | O_NOCTTY | O_NDELAY)<0)
{perror("open");
}

1.4. 编译驱动文件以及测试文件

1.4.1 makefile

KERN_DIR = ~/work/system/linux-2.6.22.6all:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m    += drv_led.o

其中KERN_DIR代表内核的位置,所以底下的make是根据linux内核中的makefile进行编译,注释如下:

  • -C:指定进入指定的目录即KERN_DIR,是内核源代码目录,调用该目录顶层下的Makefile,目标为modules。 M=$(shell pwd) | `pwd`选项让该Makefile在构造modules目标之前返回到模块源代码目录并在当前目录生成obj-m指定的xxx.o目标模块。
  • obj-m += xxx.o:指定当前目录要生成的目标模块,然后modules目标指向obj-m变量中设定的模块。

1.4.2 测试文件编译

$arm-linux-gcc -o test_fun test_fun.c

1.5. 测试

命令&注释如下

1.5.1 注册驱动

在命令段通过insmod对编译出的.ko文件进行加载。

#insmod /mnt/drv_frame.ko

这时可以通过以下命令,判断驱动初始化是否成功:

#lsmod  //查看是否创建设备节点
#ls /dev -l //查看所有的驱动(驱动名 主设备号 次设备号)
#cat /proc/device   //查看驱动是否注册成功

1.5.2 执行应用程序

如果驱动初始化没有问题就可以直接开始运行应用程序:

#./mnt/drv_fun

运行成功会看见系统调用了drv_open()函数以及drv_write()函数打印出来的值:

drv_open

drv_write

1.5.3 注销驱动

rmmod drv_led    //.ko文件名

这一步会注销驱动并且删除设备节点。若要单独删除设备节点可以直接rm -r dev/led 指令

二、LED驱动程序

2.1. 虚拟内存

在裸机程序中对于LED的点亮是直接通过物理地址进行配置的,但是在驱动程序中是通过虚拟地址这个中介对物理地址进行操作。从物理地址向虚拟地址映射可以通过ioremap(address, length)进行映射。在卸载时可以通过iounmap(address)进行去映射。

2.2. 框架添加LED驱动程序

除了对地址的映射,其他的操作和裸机配置LED操作一样

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>static struct class *leddrv_class;
static struct class_device    *leddrv_class_dev;volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;static int led_drv_open(struct inode *inode, struct file *file)
{*gpfcon &= ~((3<<8) | (3<<10) | (3<<12));*gpfcon |=  ((1<<8) | (1<<10) | (1<<12));     //printk("first_drv_open\n");     return 0;
}static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{int val;copy_from_user(&val, buf, count);          // copy buf from user space to val   if(val == 1)*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));else*gpfdat |=  ((1<<4) | (1<<5) | (1<<6));//copy_to_user()//printk("led_drv_write\n");return 0;
}static struct file_operations led_drv_fops = {.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open   =   led_drv_open,     .write    =    led_drv_write,
};
int major;
static int led_drv_init(void)
{major = register_chrdev(0, "led_drv", &led_drv_fops); // 注册, 告诉内核// register chrdev. to major (major, "drv name", file operations);leddrv_class = class_create(THIS_MODULE, "leddrv");if(IS_ERR(leddrv_class))printk( "ERROR creat key class");leddrv_class_dev = class_device_create(leddrv_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/xyz */if(leddrv_class_dev == NULL)printk("ERROR creat dev");gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //remap LED GPFCPNgpfdat = gpfcon + 1;                                        //remap LED GPFDATreturn 0;
}static void led_drv_exit(void)
{unregister_chrdev(major, "led_drv"); // 卸载class_device_unregister(leddrv_class_dev);class_destroy(leddrv_class);iounmap(gpfcon);           // unmap virtual address
}module_init(led_drv_init); //When install drv, system will find init function
module_exit(led_drv_exit);   // Uinstall drvMODULE_LICENSE("GPL");

PS:从用户空间的参数在驱动程序中成员函数如果要使用,则需要通过copy_form_user()和copy_to_user()将用户空间的参数传递过来。用法:

1、Copy_to_user( to, &from, sizeof(from))

To:用户空间函数  (可以是数组)

From:内核空间函数(可以是数组)

sizeof(from):内核空间要传递的数组的长度

2、Copy_from_user(&from , to , sizeof(to) )

To:用户空间函数  (可以是数组)

From:内核空间函数(可以是数组)

sizeof(from):内核空间要传递的数组的长度

2.3. 应用程序

2.3.1 main函数的参数

一般的main函数都是不带参数的,因此main 后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。C语言规定main函数的参数只能有两个, 习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为: main (argc,argv)C语言还规定argc(第一个形参)必须是整型变量,argv( 第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头可以写为:

main (int argc,char *argv[])

main函数的参数值是从操作系统命令行上获得的。运行一个可执行文件时,在命令行键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。

  命令行下运行可执行文件的一般形式为:可执行文件名 参数 参数……,命令行中的参数个数原则上未加限制。argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。例如有命令行为./a.out a b b d a,由于文件名a.out本身也算一个参数,所以共有6个参数,因此argc取得的值为6。argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。 指针数组的长度即为参数个数。数组元素初值由系统自动赋予。

2.3.2 测试函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
// 用法:
// led_drv on
// led_drv offint main(int argc, char **argv)
{int fd;int val = 1;fd = open("/dev/led", O_RDWR);    //允许读写if (fd < 0)printf("can't open!\n");if(argc != 2)   // 参数数量{printf("error parterner\n");   // 提示信息printf("%s <on|off>\n",argv[0]);return 0;}if(strcmp(argv[1],"on") == 0)val = 1;elseval = 0;write(fd, &val, 4);   return 0;
}

2.3. 测试

在执行应用程序之前都相同,执行应用程序时:

#drv_fun on    //打开LED
#drv_fun off   //关闭LED

三、LED驱动程序 pro

这一节里实现分别控制每一个LED,办法很多以下列出几种常用的办法:

  1. 通过主函数传入val控制LED
  2. 通过查看次设备号控制LED

为了学习,使用控制次设备号来控制LED。

3.1. 完善驱动程序

3.1.1 引用主设备号&次设备号

设备号的使用应该始终使用<linux/kdev_t.h>中定义的宏

  • 获取主设备号:MAJOR(dev_t dev) 和
  • 获取次设备号:MINOR(dev_t dev)
  • 转换成dev_类型:MKDEV(int major,int minor)      PS: 在class_device_create()函数中有使用

3.1.2 file结构体

获得设备号需要知道当前文件对应的设备节点的位置(dev/led1),这一信息体现在file结构体中,在vim中从file通过一步一步g+]可以得到inode的位置所以在write和open函数中使用MIONR()如下所示:

int minor = MINOR(file -> f_dentry -> d_inode -> i_rdev);   //write struct file *fileint minor = MINOR(inode -> i_rdev);                         //open  struct inode *inode

3.1.3 完整驱动框架

思想:基以上两点,首先在初始化函数中初始化四个设备节点(次设备号不同),通过在write和open中分析此设备号,进而判断程序动作。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/compiler.h>
#include <linux/err.h>#define DEV_NAME "leddrv"  // device name
#define LED_MAJOR 252   // major device numberstatic struct class *leddrv_class;
static struct class_device    *leddrv_class_dev[4];volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;static int led_drv_open(struct inode *inode, struct file *file)
{int minor = MINOR(inode -> i_rdev);switch(minor){case 0:{*gpfcon &= ~((3<<4*2) | (3<<5*2) | (3<<6*2));*gpfcon |=  ((1<<4*2) | (1<<5*2) | (1<<6*2));break;}case 1:{*gpfcon &= ~(3<<4*2);*gpfcon |=  (1<<4*2);break;}case 2:{*gpfcon &= ~(3<<5*2);*gpfcon |=  (1<<5*2);break;}case 3:{*gpfcon &= ~(3<<6*2);*gpfcon |=  (1<<6*2);break;}}
//    *gpfcon &= ~((3<<8) | (3<<10) | (3<<12));
//    *gpfcon |=  ((1<<8) | (1<<10) | (1<<12));     //printk("first_drv_open\n");     return 0;
}static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{int val;copy_from_user(&val, buf, sizeof(buf));          // copy buf from user space to val   int minor = MINOR(file -> f_dentry -> d_inode -> i_rdev);switch(minor){case 0:{if(val == 1)                                                                  *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));                     else                                                                                        *gpfdat |=  ((1<<4) | (1<<5) | (1<<6));break;}case 1:{if(val == 1)                                                                  *gpfdat &= ~(1<<4);                     else                                                                                        *gpfdat |=  (1<<4);break;}case 2:{if(val == 1)*gpfdat &= ~(1<<5);else*gpfdat |=  (1<<5);break;}case 3:{if(val == 1)                                                                  *gpfdat &= ~(1<<6);                     else                                                                                        *gpfdat |=  (1<<6);break;}}
/*    if(val == 1)*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));else*gpfdat |=  ((1<<4) | (1<<5) | (1<<6));*///copy_to_user()//printk("led_drv_write\n");return 0;
}static struct file_operations led_drv_fops = {.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */.open   =   led_drv_open,     .write    =    led_drv_write,
};
int major;
static int led_drv_init(void)
{major = register_chrdev(0, DEV_NAME, &led_drv_fops); // register chrdev. to major (major, "drv name", file operations);//To file_operations classleddrv_class = class_create(THIS_MODULE, "leddrv");if(IS_ERR(leddrv_class))   // error{printk( "ERROR creat key class");}//creat device leddrv_class_dev[0] = class_device_create(leddrv_class, NULL, MKDEV(major, 0), NULL, "ledall"); int minor;for(minor = 1; minor < 4; minor++){leddrv_class_dev[minor] = class_device_create(leddrv_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);if(unlikely(IS_ERR(leddrv_class_dev[minor])))return PTR_ERR(leddrv_class_dev[minor]);}//remap phcical addressgpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //remap LED GPFCPNgpfdat = gpfcon + 1;                                        //remap LED GPFDATprintk("initialized complete\n");return 0;
}static void led_drv_exit(void)
{unregister_chrdev(major, DEV_NAME); // 卸载int minor;for(minor = 0; minor < 4; minor ++){class_device_unregister(leddrv_class_dev[minor]);}class_destroy(leddrv_class);iounmap(gpfcon);           // unmap virtual address
}module_init(led_drv_init); //When install drv, system will find init function
module_exit(led_drv_exit);   // Uinstall drvMODULE_LICENSE("GPL");

3.2. 完善应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>// led_drv <on|off> <dev>void print_tips(char *app_name)
{printf("Tips:\n");printf("%s <on|off> <dev>", app_name);printf("eg.\n");printf("%s <on|off> /dev/ledall\n", app_name);printf("%s <on|off> /dev/led1\n", app_name);printf("%s <on|off> /dev/led2\n", app_name);printf("%s <on|off> /dev/led3\n", app_name);
}
int main(int argc, char **argv)
{int fd;int val = 1;char *dev_list = argv[2];fd = open(dev_list, O_RDWR);if (fd < 0){printf("can't open!\n");}if(argc != 3)   // input  {print_tips(argv[0]);return 0;}if(strcmp(argv[1],"on") == 0)val = 1;elseval = 0;write(fd, &val, 4);return 0;
}

后面就和之前的操作一样了 看码!!

S3C2440 开发板实战(7):字符设备驱动框架+LED驱动相关推荐

  1. 第12课第2.2节 字符设备驱动程序之LED驱动程序_测试改进

    第12课第2.2节 字符设备驱动程序之LED驱动程序_测试改进 //仅用flash上的根文件系统启动后,手工MOUNT NFS mount -t nfs -o nolock,vers=2 192.16 ...

  2. Linux驱动_设备树下LED驱动

    前言 学习完设备树基础知识后,完成设备树下LED驱动实验 一.修改设备树文件 在设备书根/节点下添加子节点led信息: alphaled {status = "okay";comp ...

  3. Linux驱动(12)--LED驱动

    LED驱动 1. LED驱动 1.1 LED管脚的调用配置函数 1.2 LED驱动源码与注释 1.3 LED驱动加载步骤 2. 调用LED驱动 2.1 源码 2.2 步骤 1. LED驱动 1.1 L ...

  4. 基于Cortex-A7架构的嵌入式linux ARM驱动开发<1>——字符设备驱动开发

    一.什么是字符设备 字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的.比如我们最常见的点灯.按键.IIC.SPI, L ...

  5. linux驱动的中断函数,嵌入式Linux驱动开发(四)——字符设备驱动之中断方式以及中断方式获取按键值...

    之前我们完成了关于通过查询的方式获取按键键值的驱动程序,可以参考:嵌入式Linux开发--裸板程序之中断控制器. 虽然读取键值没有什么问题,但是测试程序占用CPU过高,一直在不断的查询,资源消耗过大, ...

  6. linux驱动开发字符设备,linux驱动开发(三) 字符设备驱动框架

    还是老规矩先上代码 demo.c #include #include#include#include#include int demo_major = 250;int demo_minor = 0;i ...

  7. linux驱动编写(字符设备编写框架)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     上次我们编写了一个简单的字符设备,但是涉及的内容比较少,只有open和read两个函数 ...

  8. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  9. Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析 (iic驱动框架,i2c驱动框架)...

    转载于 : http://blog.csdn.net/zqixiao_09/article/details/50917655 关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸 ...

最新文章

  1. 不要用 SELECT *
  2. 《F# Deep Dives》书评与作者问答
  3. python需要php吗-学python需要学linux吗
  4. GraphPad Prism 9.3 MacOS 2021科研绘图统计 支持Monterey 安装使用教程
  5. oracle跨库插入数据,Oracle跨数据库查询并插入实现原理及代码
  6. pytorch默认初始化_PyTorch的初始化
  7. 浪潮云海OpenStack X版本技术贡献中国第一
  8. VMware 安装LINUX系统(一)
  9. 灵活的数据管理和展示javascript类库 - Recline.js
  10. 《Java设计模式》之代理模式 -Java动态代理(InvocationHandler) -简单实现
  11. ArcGIS操作小技巧(五)之色带-----横向(水平)图例
  12. ntlm身份验证_使用隐藏的ntlm身份验证进行内部信息公开
  13. wifi分析仪怎么看哪个信道好_专业的WiFi检测工具有哪些?如何解决wifi信号不好?...
  14. cad上样条曲线上的点太多了_CAD中如何编辑样条曲线增加夹点? 看完你就知道了...
  15. Linux安装vim不成功(没有可用的软件包)解决方法
  16. 疯狂的 Web 应用开源项目
  17. 【已解决】ocker: Error response from daemon: Conflict. The container name “/nginx“ is alrea.....
  18. VMware 15安装教程
  19. css实现文字中间横线,css实现文字居中两边横线效果的示例代码
  20. 全方位体验Windows的日历功能

热门文章

  1. mysql alter 改密码_MySql修改密码
  2. 获取钉钉企业部门用户信息
  3. Vue中directives用法--自定义指令控制按钮权限
  4. db2数据库(db2数据库安装)
  5. Google、Facebook的工程师文化到底是什么?
  6. 3NF的无损连接和保持函数依赖的分解、BCNF的无损连接的分解
  7. Mybatis的作用
  8. 5.3 matlab数据插值(线性插值、最近点插值、埃尔米特插值、三次样条插值)
  9. 人工智能时代下,Python与C/C++谁将成为人工智能核心算法选择?
  10. 软考备战:软考中级课程+参考资料