linux驱动程序编程学习

作者: binghuiliang
发布时间:2015-07-29 11:41:20

Linux驱动程序编程学习                                      

原创 无双
发表于loveunix.net
转载请注明作者与出处

应用程序与驱动程序通过设备文件进行通信

每个设备文件都有主设备号与次设备号 主设备号表示设备的类型 次设备号表示具体的设备

在内核中 就是根据主设备号来调用相应的驱动程序 驱动根据次设备号分辩具体设备以区分操作

主设备号由linux统一分配 但是也可以使用临时设备 在注册驱动时把主设备号输入为0则由内核自动分配



设备的类型:
主要类型有下面三种
字符设备:它的保存是以字符流的形式保存
块设备:它的保存是以块为单位保存,提供和字符驱动一样的接口,当然有自己块操作的接口,如mount,同时支持像字符设备那样的访问方式,
上面两个都是映射到一个设备文件,设备文件通常保存在/dev/目录下,当然你可以保存在任意地方
网络接口:用于进行网络通信,可能针对某个硬件,如网卡,或是纯软件,如loopback,网络接口只是面向数据包而不是数据流,所以内核的处理也不同,没有映射成任何设备文件,而是按unix标准给它们分配一个唯一的名字

作者: 无双 2004-05-03 20:40:08

驱动开发时注意点:
1 只提供机制,不限制策略,调用者可以按照自己的要求自己订制策略
2 权限的判断, 应该尽量让系统管理员决定而不是在驱动中指定,除非这个设备对整个系统有影响
3 安全编程,从用户进程得到的输入必须检查,返回给用户进程的内存必须初始化(如果这块内存刚好保存口令信息 或是其它就会破坏系统安全性)
4 内核不连接libc,所以不可以使用libc里面的函数 如printf等,必须使用内核自己提供的函数,如果你不知道有哪些函数,那看看内核头文件是不错的选择
5 避免名字空间污染,不需要输出的符号就不输出 如果你的符号被其它模块引用,那使用EXPORT_SYMBLE声明被输出,要输出的模块最好加上模块名前缀 避免冲突 .不对外开放的使用static限制在本地使用
6 注意可重入性问题,必须考虑到执行到某步时任务切换或是中断处理时应该怎样处理.

作者: 无双 2004-05-03 20:40:30

在2.6下开发简单的驱动

2.6驱动的开发相对以前各版本来说有了很大的改变 先是初始化与退出函数
以前使用init_module函数来声明模块初始化过程 使用cleanup_module来定义模块退出时的清除过程

而在2.6中 必须使用 module_init 标志来说明初始化函数 使用module_exit来说明清理函数

在2.4中,可以单独编译一个模块 而在2.6中,一个模块的编译,必须编译全部所有模块

下面是一个2.6内核的模板


CODE

#define MODULE
#include linux/module.h>
#include linux/config.h>
#include linux/init.h>

static int __init name_of_initialization_routine(void) {
/*
* code here
*/
}
static void __exit name_of_cleanup_routine(void) {
/*
* code here
*/
}
module_init(name_of_initialization_routine);
module_exit(name_of_cleanup_routine);



MODULE宏也不再是必须的了,在编译时可不定义

编译过程的区别
2.4的编译过程如下
gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.21/include -O2 -c testmod.c
要定义__KERNEL__是因为在内核头文件中,某些符号只在定义这个宏后才会公开

2.6的编译过程
写一个简单的Makefile只有一行
obj-m := testmod.o

然后使用如下命令编译
make -C /usr/src/linux-2.6.1 SUBDIRS=$PWD modules
$PWD是你的内核模块所在的目录 使用如上命令时会自动重编译系统的内核模块与你的模块
所以 保存内核源目录中的版本号与配置文件与你当前运行内核相同是必要的

一个简单的例子:
testmod.c
CODE

#define MODULE
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>

static int __init testmod_init(void)
{
printk("<1>hello world/n");
return 0;
}

static void __exit testmod_cleanup(void)
{
printk("<1>exit module/n");
}

module_init(testmod_init);
module_exit(testmod_cleanup);


Makefile 编译方法与执行结果
CODE

wushuang:~/work/kernel# cat Makefile
obj-m :=testmod.o

wushuang:~/work/kernel# cat test
testmod: module license 'unspecified' taints kernel.
hello world
wushuang:~/work/kernel# rmmod testmod
exit module
wushuang:~/work/kernel#

wushuang:~/work/kernel# make -C /usr/src/linux SUBDIRS=~/work/kernel/ modules

作者: 无双 2004-05-03 20:41:00

一个有用的资源
CODE

http://people.netfilter.org/~rusty/unreliable-guides/kernel-hacking/lk-hacking-guide.html

定义了一些常用的内核符号定义

作者: 无双 2004-05-04 13:08:34

第一个可用的例子

下面是一个简单的字符设备驱动程序

CODE



/*  chardev.c: Creates a read-only char device that says how many
*  times
*   *  you've read from the dev file
*    */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>


static int __init testmod_init(void);
static void __exit testmod_exit(void);

static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices   */
#define BUF_LEN 80            /* Max length of the message from the device */


/* Global variables are declared as
* static, so are global within the file.
* */

static int Major;            /* Major number assigned to our device driver */
static int Device_Open = 0;  /* Is device open?  Used to prevent multiple  */
static char msg[BUF_LEN];    /* The msg the device will give when asked    */
static char *msg_Ptr;

static struct file_operations fops = {
   .read = device_read,
   .write = device_write,
   .open = device_open,
   .release = device_release
};//注意这里需要是静态变量, 不然会出现内核访问错误情况
//.open = device_read,对fops里面的open变量赋值为device_read,
//,这是c99;里面定义的新标准,表示定义结构变量时同时对它进行子元素的赋值
// gcc扩展使用的是open:device_read,


/*                   Functions
*                       */

static int __init testmod_init(void)
{
   Major = register_chrdev(240, DEVICE_NAME, &fops);
//注册字符驱动 第一个参数是主设备号 如果第一个参数是0 表示由内核自动分配
//第二个是设备名 可以在/proc/devices里面看到
//第三个是对此设备的操作函数 具体操作可以看file_operations结构定义 在linux/fs.h里面

   if (Major < 0) {
       printk ("Registering the character device failed with %d/n", Major);
       return Major;
   }

   Major   = 240;
   printk("<1>I was assigned major number %d.  To talk to/n", Major);
   printk("<1>the driver, create a dev file with/n");
   printk("'mknod /dev/hello c %d 0'./n", Major);
   printk("<1>Try various minor numbers.  Try to cat and echo to/n");
   printk("the device file./n");
   printk("<1>Remove the device file and module when done./n");

   return 0;
}


static void __exit testmod_exit(void)
{
   /* Unregister the device */
   int ret = unregister_chrdev(Major, DEVICE_NAME);//取消注册 取消了后模块可以unload
   if (ret < 0) printk("Error in unregister_chrdev: %d/n", ret);
}  


/*                   Methods
*                       */

/* Called when a process tries to open the device file, like
*   * "cat /dev/mycharfile"
*       */
static int device_open(struct inode *inode, struct file *file)
{
   static int counter = 0;
   if (Device_Open) return -EBUSY;
   Device_Open++;
   sprintf(msg,"I already told you %d times Hello world!/n", counter++);
   msg_Ptr = msg;
   //MOD_INC_USE_COUNT;在2.6中,会自动维护引用计数,所以不再需要这个宏,但是如果要考虑兼容性那最好保存这个宏

   printk("<1>device_open call/n");
   return SUCCESS;
}


/* Called when a process closes the device file.
*   */
static int device_release(struct inode *inode, struct file *file)
{
   Device_Open --;     /* We're now ready for our next caller */

   printk("<1>device_release call/n");
   /* Decrement the usage count, or else once you
    * opened the file, you'll
    *           never get get rid of the module.
    *           */
   //MOD_DEC_USE_COUNT;

   return 0;
}


/* Called when a process, which already opened the dev file,
* attempts to
*     read from it.
*      */
static ssize_t device_read(struct file *filp,
       char *buffer,    /* The buffer to fill with data */
       size_t length,   /* The length of the buffer     */
       loff_t *offset)  /* Our offset in the file       */
{
   /* Number of bytes actually written to the buffer
    * */
   int bytes_read = 0;

   /* If we're at the end of the message, return 0
    * signifying end of file */
   if (*msg_Ptr == 0) return 0;

   /* Actually put the data into the buffer */
   while (length && *msg_Ptr)  {
//内核空间与用户空间内存是不同的,所以需要使用内存操作函数 而不可以直接赋值
       /* The buffer is in the user data
        * segment, not the kernel
        * segment;
        *       * assignment won't work.
        *       We have to use put_user
        *       which copies data from
        *               * the kernel data
        *               segment to the
        *               user data
        *               segment. */
       put_user(*(msg_Ptr++), buffer++);

       length--;
       bytes_read++;
   }

   /* Most read functions return the
    * number of bytes put into the buffer
    * */
   return bytes_read;
}


/*  Called when a process writes to dev file: echo "hi" >
*  /dev/hello */
static ssize_t device_write(struct file *filp,
       const char *buff,
       size_t len,
       loff_t *off)
{
   printk ("<1>Sorry, this operation isn't supported./n");
   return -EINVAL;
}
module_init(testmod_init);
module_exit(testmod_exit);


上面是一个简单的字符设备驱动例子
使用的makefile如下
CODE

subdir='/root/work/kernel/'
obj-m :=testmod.o
all:
make -C /usr/src/linux SUBDIRS=$(subdir) modules
clean:
-rm testmod.[^c]*
reset:clean all
-rmmod testmod
-rm *wushuang*
insmod testmod.ko
mknod testmod_wushuang0 c 240 0


使用时make reset就OK了

简单的测试办法
echo"hello">testmod_mushuang0



第一次自己写时没有把fops定义成静态的 结果每次都报错 很是郁闷 后面从网上拿到了这个程序改了改

旧的内核需要自己维护模块引用计数 所以使用MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT
新内核中自动维护 这两个已经不再使用

file_operations定义了字符设备的接口 你可以根据自己的需要添加自己要处理的函数 不需要处理的函数将为NULL 由内核自动处理


如果需要传参数 那使用module_param宏
如下
CODE

subdir='/root/work/kernel/'
obj-m :=testmod.o
all:
make -C /usr/src/linux SUBDIRS=$(subdir) modules
clean:
-rm testmod.[^c]*
reset:clean all
-rmmod testmod
-rm *wushuang*
insmod testmod.ko
mknod testmod_wushuang0 c 240 0

 

作者: 无双 2004-05-04 15:12:47

内核内可使用符号的查找

第一个办法是看/proc/kallsyms

第二个是看内核文档或是代码
http://www.kernelnewbies.org/documents/ 这里列出了很多连接 包括内核api文档
第三个是看邮件列表 文档总不可能及时更新 这时而相同的问题可能别人已经碰到过并处理完 这时就没有必要再重复一次以体现自己的伟大了

另外需要注意的是 开发时必须include正确的头文件 不然会报错 虽然为些错有些莫名奇妙(可能因为使用的宏类型比较多吧 )

头文件在linux/目录下 asm/目录下也有


如果你在linux下工作 那建议使用cscope 来帮助查找
使用方法
cd /usr/include/linux
cscope -R

然后就出现了cscope的界面
CODE

Global definition: get_user

File Line
0 uaccess.h 171 #define get_user(x,ptr) /



Find this C symbol: 查找这个符号
Find this global definition: 查找这个定义
Find functions called by this function:
Find functions calling this function:
Find this text string:
Change this text string:
Find this egrep pattern:
Find this file:
Find files #including this file:


使用tab来跳转 想退出时按ctrl-d就可以 或是ctrl-c
它可以反向查找和正向查找 很是方便 查找到后按数字或是在选中项上按回车就使用默认编辑器打开到指定页

另外已经集成在了vim中 只是vim默认编译时不打开cscope支持 如果想在vim中使用还需要自己另外编译

作者: 无双 2004-05-04 15:15:07

得到调用驱动者信息

在内核中有一个全局变量current
它是一个task_strut的指针 定义在asm/current.h
task_struct定义在linux/scked.h

这个变量指向了当前调用者信息

如果你想取调用者信息的话 可以取出它的值

下面是一个简单的例子

CODE

static ssize_t device_read(struct file *filp,
       char *buffer,    /* The buffer to fill with data */
       size_t length,   /* The length of the buffer     */
       loff_t *offset)  /* Our offset in the file       */
{
       char outbuf[2048];
       int len;
       if(!buffer || length <1)
          return 0;
       len = dump_processinfo(outbuf,2047);
       outbuf[2047] = 0;
       if(len >length)
           len = length;
   copy_to_user(buffer,outbuf,len);
   return len;
}

static size_t dump_processinfo(char *buf,int len)
{
   if(!buf || len <1 )
       return 0;
   return snprintf(buf,len,"stat:%d actvated:%d pid:%d cmd:%s/n",
          current->state,current->activated,current->pid,current->comm );
}


把上面的device_read代替上一贴中的device_read然后重编译

简单测试方法就是
cat testmod_mushuang0
使用ctrl-c停止

注意到里面使用了copy_to_user 内核空间中的内存与用户空间的不一样 所以需要使用内存操作函数把内核中信息复制到用户空间去 同理还有上面的put_user 也是一样的功能

对应的,内核空间需要读用户空间数据时,使用copy_from_user ,get_user


get_user和put_user只是指定了源和目的 没有使用长度 那它是如何复制的呢 是不是只复制一个字节或是指定的字节 这个开始时容易让人感到困惑
它们复制的长度是根据第二个数的类型来判断的

也就是 put_user(x,y) = copy_to_user(x,y,sizeof(*y)) 只是性能更快 注意y 应该是内建类型 也就是如char short int类型

内存具体信息可以在网上查找
KernelHacking101.pdf

里面讲解的不错

 

作者: carol 2004-05-06 16:41:56

学习 happy.gif

作者: 无间道 2004-05-07 20:01:58

linux驱动编程原来学过字符设备编程,不过有一段时间没用过了,没机会练手。

作者: 无双 2004-05-08 12:04:52

偶只是随意写的 想到哪里写到哪里


模块在内核中的实例个数

在应用开发时 每个进程的地址空间都是独立的 改变一个进程的值 不会影响其它进程

但是在内核中是不是也会这样呢


写一个简单的驱动程序

如下





/* chardev.c: Creates a read-only char device that says how many
* times
* * you've read from the dev file
* */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>


static int __init testmod_init(void);
static void __exit testmod_exit(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static size_t dump_processinfo(char *buf,int len);

#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */


/* Global variables are declared as
* static, so are global within the file.
* */

static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open? Used to prevent multiple */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;

static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};


/* Functions
* */

static int __init testmod_init(void)
{
Major = register_chrdev(240, DEVICE_NAME, &fops);

if (Major < 0) {
printk ("Registering the character device failed with %d/n", Major);
return Major;
}

Major = 240;
printk("<1>I was assigned major number %d. To talk to/n", Major);
printk("<1>the driver, create a dev file with/n");
printk("'mknod /dev/hello c %d 0'./n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to/n");
printk("the device file./n");
printk("<1>Remove the device file and module when done./n");

return 0;
}


static void __exit testmod_exit(void)
{
/* Unregister the device */
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0) printk("Error in unregister_chrdev: %d/n", ret);
}


/* Methods
* */

/* Called when a process tries to open the device file, like
* * "cat /dev/mycharfile"
* */
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open) return -EBUSY;
Device_Open++;
sprintf(msg,"I already told you %d times Hello world!/n", counter++);
msg_Ptr = msg;
//MOD_INC_USE_COUNT;

printk("<1>device_open call/n");
return SUCCESS;
}


/* Called when a process closes the device file.
* */
static int device_release(struct inode *inode, struct file *file)
{
Device_Open --; /* We're now ready for our next caller */

printk("<1>device_release call/n");
/* Decrement the usage count, or else once you
* opened the file, you'll
* never get get rid of the module.
* */
//MOD_DEC_USE_COUNT;

return 0;
}


/* Called when a process, which already opened the dev file,
* attempts to
* read from it.
* */
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
char outbuf[2048];
int len = dump_processinfo(outbuf,2047);
outbuf[2047] = 0;
if(len >length)
len = length;
copy_to_user(buffer,outbuf,len);
return len;
/* Most read functions return the
* number of bytes put into the buffer
* */
//return bytes_read;
}


/* Called when a process writes to dev file: echo "hi" >
* /dev/hello */
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t *off)
{
printk ("<1>Sorry, this operation isn't supported./n");
return -EINVAL;
}

static size_t dump_processinfo(char *buf,int len)
{
static int i = 0;
if(!buf || len <1 )
return 0;

printk(KERN_INFO"%dth dump/n",i++);
return 0;
}


module_init(testmod_init);
module_exit(testmod_exit);





这只是在上面的例子基础上进行了一些修改 注意在dump_processinfo里面使用了一个静态变量

测试步骤如下:
编译上面的模块 然后使用insmod testmod.ko加载模块

mknod testmod_wushuang c 240 0
mknod testmod_wushuang1 c 240 1

cat testmod_wushuang
cat testmod_wushuang
cat testmod_wushuang1
cat testmod_wushuang1

看上面的输出结果 发现虽然是不同的进程 对不同的文件进行操作 但是序号还是在增加中

rmmod testmod
insmod testmod

重加载内核模块

然后重复上面的步骤

发现重加载后 计数器从头开始了

可见驱动模块在整个系统中是唯一的 另外 每次模块unload时 内核都会把它的内存空间翻译

所以 在模块开发中要慎用静态变量

作者: 无双 2004-05-09 21:07:45

可重入的进一步研究

即然模块在内存中只有一份 那想操作多个文件时什么办

这时考虑的就是看open操作会不会被调用了

对任何操作 都是先调用open 再进行读写操作 最后调用close(ioctl还没江过)

如果你的程序中只调用open而没有调用close时 那会怎样呢

答案是 你没有调用close时 在进程退出时操作系统也会调用close 所以不会引起资源泄漏

在这里可以补充一下c中进程三种退出方式的区别
第一个是普通的return 这种与exit相同
第二个是使用exit,刷新所有打开文件并close,调用使用atexit 注册的退出清理函数进行退出清理
第三个是_exit,只是关闭文件 ,不调用atexit注册的退出函数,也不刷新IO,这可能会引起数据的不同步 但是速度比exit快

作者: 无双 2004-05-09 21:12:57

每次对设备的操作都会调用open
并且最后也会调用close 所以 可以考虑在open中对不同的设备进行区分

open传入两个参数 第一个是inode类型第二个是file类型
我们知道 每个文件系统上的I节点值是唯一的 从整个系统来讲 也就是inode与主/次设备号的组合唯一
这时可以考虑使用inode信息来对每个文件生成一个唯一的信息

对设备来说 可能是把同一主设备次设备号的文件看成同一个设备 对它共用操作 这时可以只看inode的主次设备号



另外在open时会传入一个inode的值 里面就是这个文件的inode值

写一个简单的程序测试上面的知识:
测试内核文件 在上面的基础上修改 增加打印inode节点值功能

CODE

static int device_open(struct inode *inode, struct file *file)
{
   static int counter = 0;
   if (Device_Open) return -EBUSY;
   Device_Open++;
   sprintf(msg,"I already told you %d times Hello world!/n", counter++);
   msg_Ptr = msg;
   //MOD_INC_USE_COUNT;

   printk("<1>device_open call inode info:%d/n",inode->i_ino);
   return SUCCESS;
}


/* Called when a process closes the device file.
*   */
static int device_release(struct inode *inode, struct file *file)
{
   Device_Open --;     /* We're now ready for our next caller */

   printk("<1>device_release call inode:%d/n",inode->i_ino);
   /* Decrement the usage count, or else once you
    * opened the file, you'll
    *           never get get rid of the module.
    *           */
   //MOD_DEC_USE_COUNT;

   return 0;
}



测试文件
CODE

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>

int main()
{
   char buf[4096];
   int len = sizeof(buf);
   int fp = open("../kernel/testmod_wushuang0",S_IREAD);

   return 0;
}


编译上面的程序并执行
看看输出 发现执行了release
另外
使用ls -i ../kernel/testmod_wushuang0
看到输出是一样的 说明i_ino保存的就是i节点值

下面是打印出inode信息的一个例子

 

作者: 无双 2004-05-11 22:05:22

inode信息 后面补充完全
现在从这里得到参考 我想更好的理解还是多编程
http://www.win.tue.nl/~aeb/linux/lk/lk-10.html#ss8.6

CODE

struct inode {
         struct hlist_node       i_hash;散列 为了更快的查找到这个inode
         struct list_head        i_list;inode链表,使用inode_lock保护
         struct list_head        i_dentry;目录入口,可以从这个访问到上一级目录 及文件名等相关信息 还有链接
         unsigned long           i_ino;i节点值 在一个文件系统中唯一 也就是在同一主次设备号的设备上唯一
         atomic_t                i_count;使用数目
         umode_t                 i_mode;访问模式
         unsigned int            i_nlink;引用数目
         uid_t                   i_uid;用户id
         gid_t                   i_gid;组id
         dev_t                   i_rdev;设备号 通过MAJOR与MIRROR得到主设备与从设备
         loff_t                  i_size;
         struct timespec         i_atime;最后访问时间
         struct timespec         i_mtime;修改时间
         struct timespec         i_ctime;创建时间
         unsigned int            i_blkbits;
         unsigned long           i_blksize;
         unsigned long           i_version;
         unsigned long           i_blocks;
         unsigned short          i_bytes;
        spinlock_t              i_lock; /* i_blocks, i_bytes, maybe i_size */
         struct semaphore        i_sem;
         struct inode_operations *i_op;
         struct file_operations  *i_fop; /* former ->i_op->default_file_ops */
         struct super_block      *i_sb;
         struct file_lock        *i_flock;
         struct address_space    *i_mapping;
         struct address_space    i_data;
         struct dquot            *i_dquot[MAXQUOTAS];
         /* These three should probably be a union */
         struct list_head        i_devices;inode属于某个块设备 这里得到设备列表 有bdev_lock保护
         struct pipe_inode_info  *i_pipe;
         struct block_device     *i_bdev;
         struct cdev             *i_cdev;
         int                     i_cindex;
 
         unsigned long           i_dnotify_mask; /* Directory notify events */
         struct dnotify_struct   *i_dnotify; /* for directory notifications */
 
         unsigned long           i_state;
 
         unsigned int            i_flags;
       unsigned char           i_sock;
 
         atomic_t                i_writecount;
         void                    *i_security;
         __u32                   i_generation;
         union {
                 void            *generic_ip;
         } u;
 #ifdef __NEED_I_SIZE_ORDERED
         seqcount_t              i_size_seqcount;
 #endif
 };


 

作者: 无双 2004-05-1:14:48

file结构的定义
file结构在字符类型的驱动中也是重要的一个结构
所以 想开发驱动 我们还是有必要了解它的内容

CODE

struct file {
         struct list_head        f_list;
         struct dentry           *f_dentry;//目录树入口
         struct vfsmount         *f_vfsmnt;//指向vfs的入口
         struct file_operations  *f_op;//文件操作函数指针,一般不修改,除非你要根据不同的文件指定不同的操作
         atomic_t                f_count;//文件的引用计数
         unsigned int            f_flags;//文件的标志
         mode_t                  f_mode;//文件的模式
         loff_t                  f_pos;//当前文件的访问偏移
         struct fown_struct      f_owner;//文件的拥有者(打开进程)
         unsigned int            f_uid, f_gid;//文件属主
         int                     f_error;//文件的错误码
         struct file_ra_state    f_ra;//预取信息
 
         unsigned long           f_version;//
         void                    *f_security;
 
         /* needed for tty driver, and maybe others */
         void                    *private_data;
        /* Used by fs/eventpoll.c to link all the hooks to this file */
         struct list_head        f_ep_links;
         spinlock_t              f_ep_lock;
 };


下面是网上资料
CODE

5.1 File Structure

The file structure is defined in linux/fs.h to be:

struct fown_struct {
       int pid;                /* pid or -pgrp where SIGIO should be sent */
       uid_t uid, euid;        /* uid/euid of process setting the owner */
       int signum;             /* posix.1b rt signal to be delivered on IO */
};

struct file {
       struct list_head        f_list;
       struct dentry           *f_dentry;
       struct file_operations  *f_op;
       atomic_t                f_count;
       unsigned int            f_flags;
       mode_t                  f_mode;
       loff_t                  f_pos;
       unsigned long           f_reada, f_ramax, f_raend, f_ralen, f_rawin;
       struct fown_struct      f_owner;
       unsigned int            f_uid, f_gid;
       int                     f_error;

       unsigned long           f_version;

       /* needed for tty driver, and maybe others */
       void                    *private_data;//驱动自己使用 内核不修改它 当第一次创建时内核会把它设置成NULL 在驱动中可以用它来保存某个状态
};


刚刚找到了这个站点 上面有这个结构的进一步说明 高兴
http://cgi.cse.unsw.edu.au/~neilb/oss/linux-commentary/vfs-5.html
CODE

The fields have the following meaning:

f_list

   This field links files together into one of a number of lists. There is one list for each active file-system, starting at the s_files pointer in the super-block. There is one for free file structures (free_list in fs/file_table.c). And there is one for anonymous files (anon_list in fs/file_table.c) such as pipes.

f_dentry

   This field records the dcache entry that points to the inode for this file. If the inode refers to an object, such as a pipe, which isn't in a regular file-system, the dentry is a root dentry created with d_alloc_root.

f_op

   This field points to the methods to use on this file.

f_count

   The number of references to this file. One for each different user-process file descriptor, plus one for each internal usage.

f_flags

   This field stores the flags for this file such as access type (read/write), nonblocking, appendonly etc. These are defined in the per-architecture include file asm/fcntl.h. Some of these flags are only relevant at the time of opening, and are not stored in f_flags. These excluded flags are O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC. This list is from filp_open in fs/open.c.

f_mode

   The bottom two bits of f_flags encode read and write access in a way that it is not easy to extract the individual read and write access information. f_mode stores the read and write access as two separate bits.

f_pos

   This records the current file position which will be the address used for the next read request, and for the next write request if the file does NOT have the O_APPEND flag.

f_reada, f_remax, f_raend, f_ralen, f_rawin

   These five fields are used to keeping track of sequential access patterns on the file, and determining how much read-ahead to do. There may be a separate section on read-ahead.

f_owner

   This structure stores a process id and a signal to send to the process when certain events happen with the file, such as new data being available. Currently, keyboards, mice, serial ports and network sockes seem to be the only files which is this feature (via kill_fasync).

f_uid, f_gid

   These fields get set to the owner and group of the process which opened the file. They don't seem to be used at all.

f_error

   This is used by the NFS client file-system code to return write errors. It is set in fs/nfs/write.c and checked in fs/nfs/file.c, and used in mm/filemap.c:generic_file_write

f_version

   This field is available to be used by the underlying file-system to help cache state, and check for the cache being invalid. It is changed whenever the file has its f_pos value changed.

   For example, the ext2 file-system uses it in conjuction with the i_version field in the inode to detect when a directory may have changed. If neither the directory nor the file position has changed, then ext2 can be sure that the current file position is the start of a valid directory entry, otherwise it much re-check from the start of the block.

private_data

   This is used by many device drivers, and even a few file-systems, to store extra per-open-file information (such as credentials in coda).


 

作者: 无双 2004-05-1:29:41

file结构是在内核中存在 由内核维护 它与c库里面的FILE没有关系

一个file文件对应一个打开的文件 由内核在打开文件前创建并置各值 如f_ops
一个file结构可能被多个进程引用 如在父子进程中
也可能多个file结构指向同一个文件 如不同进程间打开同一个文件

上面的inode也只是一个内存中的inode 而不是磁盘上的inode
磁盘上有很多inode 而它们平台多数都不会使用到
所以内核江不会启动时就把它们读到内存 而只是在一个inode需要时才读到内存中
这时它会创建一个inode,初始化.
当这个inode被改变时,把它写到磁盘上去

进一步阅读资料
http://www.jhpu.net/wsktang/operating/

我想这些东西并不是三言二语可以讲清楚的 所以参考上面的资料是了解操作系统实现的更好办法

作者: qinxj 2004-05-18 17:20:50

无双兄的技术一流,没想到写作水平也很高啊,佩服佩服!

作者: 无双 2004-05-18 21:36:47

我都不懂我在写什么awkard.gif



内核中的同步机制

如果多个进程或是线程同时想改变一个变量 或说程序间的运行结果精确依赖于它的执行顺序 这种情况就叫做竞态
为了让程序的运行结果可控 于是发明了同步机制

在unix下
进程间的同步方式有管道,消息队列,信号量,信号,共享内存
线程间的同步工有互斥锁,条件变量,

那在内核开发中 也需要有同样的同步机制

内核开发中与应用开发的区别是
内核开发更重视高效率 并且 对可重入性的要求更高
操作系统中只有一份内核的映象 内核模块可以访问系统所有资源并修改 影响整个系统
而应用程序则每个进程都有自己的地址空间,每个进程的活动不影响其它进程
内核是中断式驱动的 每一步都需要考虑可重入性
进程除非指定信号处理程序 否则是可以自己控制程序的运行
另外对多cpu的情况 必须保证内核中变量的同步 而多CPU对进程来说已经被抽像了 它不必了解

下面把内核中使用的内核锁机制写一下

第一个是关中断与开中断
disable_irq(unsigned int irq)//等待到irq的处理已经完成才返回
disable_irq_nodync(unsigned int irq)//返回前并不保证irq的处理函数已经完成
enable_irq(unsigned int irq)

原子化操作
如果指令的执行是一次完成 中间没有中断 我们把它叫做原子化操作
原子操作是一个atomic_t类型的变量,这个类型的具体实现是什么对外面来说并不需要关心
因为内核已经定义了一组操作函数
atomic_t类型的有效值是24bit,注意
所有函数的定义都在asm/atomic.h

CODE

atomic_read — read atomic variable
atomic_set — set atomic variable
atomic_add — add integer to atomic variable
atomic_sub — subtract the atomic variable
atomic_sub_and_test — subtract value from variable and test result
atomic_inc — increment atomic variable
atomic_dec — decrement atomic variable
atomic_dec_and_test — decrement and test
atomic_inc_and_test — increment and test
atomic_add_negative — add and test if negative//如果<0返回true,>=0为false

带test后缀的函数,判断操作后结果是不是==0,如果结果==0那返回true
不然返回false


自旋锁
自旋锁为需要快速完成的操作提供了一个简单的机制,当它发现锁已被锁定时,会循环等待一直到可用,为变量的同步访问提供了机制
与信号量相比,自旋锁的速度更快 开销更少
它的定义在asm/spinlock.h
类型为spinlock_t
有一个快速初始化宏SPINLOCK_UNLICKED;
它提供的函数如下
CODE

void spin_lock_init(spinlock_t *sl); //可使用sl = SPINLOCK_UNLICKED;代替
int spin_is_locked(spinlock_t *sl); //检测是不是已被锁定
void spin_lock(spinlock_t *sl);//锁定
void spin_unlock(spinlock_t *sl);//解锁
int spin_trylock(spinlock_t *sl); /* returns 0 if succeeded */
//下面在需要禁止irq时使用,当然速度会降低
spin_lock_irqsave(spinlock_t*sl,unsigned long flags)
spin_lock_irqrestore(spinlock_t*sl,unsigned long flags)



读写锁
读写锁用于更细的锁定
类型为rwlock_t
也定义在spilock.h
初始化变量为RELOCK_UNLOCKED
函数
CODE

read_lock(rwlock_t*)
read_lock_irqsave(rwlock_t*,unsigned long flags)
read_unlock(rwlock_t*)
read_unlock_irqrestore(rwlock_t(,unsigned long)
write_lock...

功能与spinlock的操作相似


信号量
信号量的功能与应用程序开发中的差不多
类型为semaphore
定义在semaphore.h
CODE

void sema_init(struct semaphore *sem, int val); /* alternative to DECLARE_... */
void down(struct semaphore *sem); /* may sleep */
int down_interruptible(struct semaphore *sem); /* may sleep; returns -EINTR on interrupt */
int down_trylock(struct semaphone *sem); /* returns 0 if succeeded; will no sleep */
void up(struct semaphore *sem);


内核锁
内核锁用于smp系统中,保证当前只有一个cpu运行 现在它的功能已被细分到各子锁了 也不再那么有用 但是在需要的时候 还是应该想到它
用法很简单 就两个函数
CODE

lock_kernel();
/* critical region ... */
unlock_kernel();


可抢占式锁定
新内核的进程调度方式增加了可抢占性 它充许更高优先级的进程运行,甚至当前进程仍在内核中运行
这时给锁定带来了一定的困难 因为锁只是针对某个cpu而言的
如果你希望使进程不可以抢占运行 那可以使用下面两个函数
preempt_disable()
preempt_enable()
它们可以调用多次,当然你加了n次锁也必须解n次锁

 

作者: 无双 2004-05-18 21:38:17

补充
信号量是 == 0 时阻塞 >0时可运行
up是 ++
down 是--

作者: sutao772 2004-06-05 15:41:38

请教无双兄一个的驱动程序版本倚赖的问题:偶刚开始学驱动,谢谢!在RH9.0上
hello.c:
#define MODULE
#include <linux/module.h>
int init_module(void) { printk("<1>Hello, world!/n"); return 0; }
void cleanup_module(void) { printk("<1>Goodbye, cruel world!/n"); }
编译/运行后
#gcc -c hello.c
#insmod ./hello.o
hello.o: kernel-module version mismatch
hello.o was compiled for kernel version 2.4.20
while this kernel is version 2.4.20-8.
请无双兄指教 ,谢谢!急

作者: 无间道 2004-06-05 23:54:11

insmod ./hello.o -f

作者: 无双 2004-06-06 07:08:37

同意无间道的

这是你打开了内核模块版本验证引起的

添加下面一行
#include "linux/version.h"
看看

作者: sutao772 2004-06-07 09:14:00

我试了一下,还是不行,情况照旧.

作者: 无双 2004-06-07 11:40:21

sad.gif

你的内核源码版本是不是与当前内核版本不一致

作者: sutao772 2004-06-08 11:25:20

我专程看了一下,头文件所在的内核源码版本和系统(RH9)的内核版本是一致的,均为2.4.20-8.
以下是makefile:

CC=gcc
MODCFLAGS := -O2 -D__KERNEL__ -I /usr/src/linux-2.4.20-8/include/linux

hello.o: hello.c
$(CC) $(MODCFLAGS) -c hello.c

clean :
rm -rf *.o


(hello.c中已添加了version.h头文件)
情况还是照旧,肯定是内核版本验证引起的(为兼容不同内核版本,我不得不这样),但我现在真的不知如何解决?还请高手多多指点,大家可以自己试试 sad.gif

作者: 无双 2004-06-08 13:16:50

google吧
google
kernel-module version mismatch

下面是一个资料 看看是不是对自己有用

我使用的2.6内核

--------------------------------------------------------------------------------
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: kernel-module version mismatch

--------------------------------------------------------------------------------

To: Andrew D Dixon <dixon99@alumni.middlebury.edu>
Subject: Re: kernel-module version mismatch
From: "John H. Robinson, IV" <jhriv@ucsd.edu>
Date: Tue, 13 Mar 2001 15:10:39 -0800
Cc: debian-mentors@lists.debian.org
In-Reply-To: <005601c0ac0f$afc034b0$0110ac@Seranoa.com>; from andrew_dwight_dixon@yahoo.com on Tue, Mar 13, 2001 at 05:48:13PM -0500
Mail-Followup-To: Andrew D Dixon <dixon99@alumni.middlebury.edu>,debian-mentors@lists.debian.org
References: <005601c0ac0f$afc034b0$0110ac@Seranoa.com>
User-Agent: Mutt/1.2.5i

--------------------------------------------------------------------------------

On Tue, Mar 13, 2001 at 05:48:13PM -0500, Andrew D Dixon wrote:
> Hi All,
> I'm writing my first module and when I try to run it I get this error:
>
> #insmod hello.o
> hello.o: kernel-module version mismatch
> hello.o was compiled for kernel version 2.2.15
> while this kernel is version 2.2.18pre21.
>
> I believe that I need to upgrade linux/module.h and I was wondering what the
> preferred method of doing this was.

/usr/include/linux/module.h correctly refers to the kernel that the
libraries were built against.

if you know where your current kernel headers are, you can include the
path to them with the -I flag

gcc -c hello.c -I/usr/src/linux/include

-john




--------------------------------------------------------------------------------

References:
kernel-module version mismatch
From: "Andrew D Dixon" <andrew_dwight_dixon@yahoo.com>
Prev by Date: One source package, multiple binary packages with different version numbers - courier
Next by Date: Re: Looking for Advocate
Prev by thread: kernel-module version mismatch
Next by thread: One source package, multiple binary packages with different version numbers - courier
Index(es):
Date
Thread

作者: hyzable 2004-06-12 10:49:42

斑竹,请教一个问题,如果在一块嵌入式的板子里希望实现自己的中断机制,应该如何处理自己写好的模块呢?是不是要将原来的板子的linux中断机制覆盖掉,要的话应该怎么覆盖?

作者: 无双 2004-06-13 16:05:06

不用吧
我现在正在出差
手边没有资料

不过
可以查查linux中断的编写方法

linux里面已经提供了安装中断的办法与接口 不用自己修改内核

作者: 无双 2004-06-13 16:05:43

看这里
http://people.netfilter.org/~rusty/unreliable-guides/kernel-hacking/lk-hacking-guide.html

作者: hyzable 2004-06-14 13:45:38

我就想知道,如果我想申请一个自己的中断,我是不是要知道具体操作哪个地址或者寄存器,不知道斑竹有没有这方面的例子教程?如果不用intel的芯片,又应该怎么做?

作者: hyzable 2004-06-19 09:19:43

模仿斑竹的简单的字符设备驱动例子写了一下,当insmod的时候,会报unresolved unregister_chrdev和register_chrdev,我用的是2.4的内核
还发现一个情况,如果定义了MODULE
gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.21/include -O2 -c testmod.c会说MODULE重定义
问题找到了,我把chrdev写成了chardev,奇怪,为什么在编译的时候不指出这个错误,反而在加载进去的时候才出错呢?

作者: hyzable 2004-06-28 17:56:19

我insmod成功了,但是有这个
Warning: loading test.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module test loaded, with warnings

还有就是我用open(“/dev/mychar”)不成功,不知道是不是路径没有设对

作者: hyzable 2004-06-28 17:56:32

我insmod成功了,但是有这个
Warning: loading test.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module test loaded, with warnings
网站的英文说:modules without available source code under a free software license). As the source is not freely available, any bugs uncovered whilst such modules are loaded cannot be investigated by the kernel hackers.

还有就是我用open(“/dev/mychar”)不成功,不知道是不是路径没有设对

作者: 无双 2004-06-28 18:04:57

Warning: loading test.o will taint the kernel: no license

2.6内核增加了 MODULE_LICENSE 用来说明内核版本信息

open不成功 是在内核中还是应用程序中

作者: hyzable 2004-06-29 09:05:34

应用程序,我是按照你的写法,写完了test。o之后,直接写了个应用程序open(/dev/mychar),是不是还需要request religion之类的?

作者: 无双 2004-06-29 13:20:55

/dev/mychar这个文件有没有
并且主次设备号正确 

标签: Linux 编程
来源:http://blog.csdn.net/binghuiliang/article/details/1868678

推荐: