免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 22684 | 回复: 40
打印 上一主题 下一主题

对LDD3前七章的一个小节 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-11-12 21:35 |只看该作者 |倒序浏览
最近又重读了LDD3(Linux Device Driver, 3rd Edition) , 对LDD的前面一部分, 即如何写一个字符设备进行了一个小结. 希望能够对初学者有用.

对于下面这一段代码, 演示了如何创建一个字符设备, 涉及到如下知识点:
1. 如何创建字符设备, 2. 使用信号量进行互斥, 3. 创建等待队列, 4. 设计一个定时器
分别包括了LDD3中第二, 三, 五, 六, 七章中的小部分内容.


  1. #include<linux/module.h>
  2. #include<linux/kernel.h>
  3. #include<linux/init.h>
  4. #include<linux/cdev.h>
  5. #include<linux/types.h>
  6. #include<linux/fs.h>
  7. #include<linux/sched.h>
  8. #include<linux/delay.h>
  9. #include<asm/semaphore.h>
  10. #include<asm/uaccess.h>

  11. MODULE_LICENSE("GPL");

  12. static struct semaphore my_lock;
  13. static wait_queue_head_t my_wait;
  14. static int flag = 0;
  15. static int major;
  16. struct timer_list my_timer;


  17. /*
  18. * my_handler 是定时器处理函数, 其将flag设定为1, 并唤醒等待队列上的进程.
  19. */
  20. void my_handler(unsigned long data)
  21. {
  22.         printk("in handler function\n");
  23.         flag = 1;
  24.         wake_up_interruptible(&my_wait);
  25.         printk("wake it up\n");
  26. }
  27. /*
  28. * my_read 由char_read调用, 当flag为0时就将自己放置到等待队列中去. 并阻塞当前进程
  29. */
  30. int my_read(int i)
  31. {
  32.         printk("waiting...\n");
  33.         wait_event_interruptible(my_wait, flag != 0);
  34.         printk("I am waked\n");
  35.         flag = 0;
  36.         return (i + 10);
  37. }
  38. /*
  39. * char_open 是打开设备时调用的函数, try_module_get()表示当前模块正在被使用中
  40. */
  41. static int char_open(struct inode *i, struct file* filp)
  42. {
  43.         printk("char_open is called\n");
  44.         try_module_get(THIS_MODULE);
  45.         return 0;
  46. }

  47. static int char_release(struct inode *i, struct file* filp)
  48. {
  49.         module_put(THIS_MODULE);
  50.         return 0;
  51. }
  52. /*
  53. * char_read 是读设备时调用的函数, 首先使用信号量完成互斥操作, 然后进行定时器的初始化, 5*HZ表示等待5秒钟.
  54. * 最后调用copy_to_user将数据拷贝给用户态的程序.
  55. */
  56. static ssize_t char_read(struct file* filp, char *buffer, size_t count, loff_t *ppos)
  57. {
  58.         int ret = 0;
  59.         if (down_interruptible(&my_lock))
  60.                 return -ERESTARTSYS;
  61.         printk("char_read is called\n");
  62.         init_timer(&my_timer);
  63.         my_timer.expires = jiffies + 5 * HZ;
  64.         my_timer.data = 0;
  65.         my_timer.function = my_handler;
  66.         add_timer(&my_timer);

  67.         ret = my_read(0);
  68.         printk("return value is %d\n", ret);
  69.         del_timer(&my_timer);
  70.         copy_to_user(buffer, (char*)&ret, sizeof(int));
  71.         up(&my_lock);
  72.         return (sizeof(int));
  73. }

  74. static struct file_operations char_fops =
  75. {
  76.         .open = char_open,
  77.         .read = char_read,
  78.         .release = char_release
  79. };

  80. static struct cdev *my_cdev;
  81. /*
  82. * char_init 模块的注册函数.
  83. */
  84. static int __init char_init(void)
  85. {
  86.         int err;
  87.         dev_t devid;
  88.         alloc_chrdev_region(&devid, 0, 1, "chardev");
  89.         major = MAJOR(devid);
  90.         my_cdev = cdev_alloc();
  91.         cdev_init(my_cdev, &char_fops);
  92.         err = cdev_add(my_cdev, devid, 1);
  93.         if (err)
  94.         {
  95.                 printk("error\n");
  96.                 return -1;
  97.         }
  98.         printk("The major number is %d\n", major);
  99.         init_MUTEX(&my_lock);
  100.         init_waitqueue_head(&my_wait);
  101.         return 0;
  102. }

  103. static void __exit char_exit(void)
  104. {
  105.         cdev_del(my_cdev);
  106.         printk("rmmod is called\n");
  107. }

  108. module_init(char_init);
  109. module_exit(char_exit);
复制代码


下面对代码进行一些简要的说明.
1. 首先是char_init函数, 该函数完成了对字符设备的注册功能. 如果注册成功, 则输出字符设备的主设备号. 另一方面, 还完成了对信号量的初始化, 以及对等待队列的初始化. 因此, 当insmod结束之后, 正常情况下就完成了对字符设备的注册等一系列的初始化动作了.
2. 然后再看打开设备的操作char_open. 当你在程序中调用open时, 就会去调用这个函数. 这个函数中有一个try_module_get(THIS_MODULE);它表示当前这个模块处于使用之中. 此时通过lsmod可以看到used一栏中不为0. 如果此时调用rmmod去卸载这个模块, 则会出错. 提示当前模块正在使用中, 不能被卸载.
3. 打开之后就是去读这个字符设备. 此时在程序中调用read时, 就会去调用模块中的char_read这个函数. 这个函数首先调用down_interruptible函数, 进行锁定. 这样, 只有一个进程能够运行char_read这个函数. 然后再是初始化定时器. 时间为5秒钟. 之后再去调用my_read这个函数. 在这个函数中检查flag是否为0, 如果为0了, 则将当前进程放到等待队列中去, 等待其它的进程将自己唤醒. 当过了五秒钟之后, 由于定时器的作用, 调用了my_handler这个函数, 它一方面将flag的值设为1, 另一方面调用wake_up_interruptible将该等待队列上的进程唤醒. 因此, 此时my_read函数又开始继续往下面执行了. 剩下的工作就是删除定时器, 并将值传递给用户态的程序.
4. 最后用户态的程序调用close()时则关于了这个设备, 此时调用了module_put(THIS_MODULE); 与之前的try_module_get相对应, 即表示当前模块没有被占用, 此时可以调用rmmod将模块卸载掉.

下面就编写一个用户态的程序来试验这个字符设备
test.c

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<unistd.h>
  4. #include<fcntl.h>
  5. #include<sys/types.h>

  6. int main()
  7. {
  8.         int fd;
  9.         int i;
  10.         fd = open("/dev/chardev", O_RDONLY);
  11.         if (fd < 0)
  12.         {
  13.                 printf("error\n");
  14.                 return -1;
  15.         }

  16.         if (read(fd, &i, sizeof(int)) < 0)
  17.         {
  18.                 printf("read error\n");
  19.                 return -1;
  20.         }
  21.         printf("i = %d\n", i);
  22.         return 0;
  23. }
复制代码


最后是实验步骤.
1. 首先编写Makefile.

  1. obj-m+=mytime.o
  2. KERNELDIR:=/lib/modules/$(shell uname -r)/build

  3. default:
  4.         make -C $(KERNELDIR) M=$(shell pwd) modules
  5. install:
  6.         insmod mytime.ko
  7. uninstall:
  8.         rmmod mytime.ko
  9. clean:
  10.         make -C $(KERNELDIR) M=$(shell pwd) clean
复制代码


2. 在当前目录下使用make进行编译, 然后再使用make install将模块加载进内核. 此时使用dmesg可以查看系统给该字符设备分配的设备号是多少. 假如为253. 则创建该字符设备. mknod /dev/chardev c 253 0
3. 编译test.c, 然后运行, 在运行时, 其等待了5秒钟, 即为等待定时器的那个过程. 最后得到输出的结果. 此时再通过dmesg命令可以查看模块中函数的调用过程.
4. 实验完之后调用make uninstall卸载该模块, 并调用make clean清除一些临时文件.

下面的结果为我在自己的机器上运行后通过dmesg打印出来的结果.
The major number is 253
char_open is called
char_read is called
waiting...
in handler function
wake it up
I am waked
return value is 10


    另外, 如果test程序在等待时执行rmmod chardev时会提示设备正在忙. 就是前面的try_module_get()所引起的. 同时如果在两个终端运行test这个程序, 则第二个test程序会阻塞, 并在第一个test程序结束之后才得以执行. 防止了并发.
    上面对字符设备的注册, 信号量, 等待队列以及定时器的使用作了一个简单的介绍, 当然, LDD3前七章的内容远不止这些. 还有很多需要去参透的地方.  
    我以前第一次看LDD的时候, 对里面的很多东西也不理解, 第一次看完之后完全不知道自己看了什么. 所以写这篇文章希望能对刚开始看LDD的朋友有一点用处.

[ 本帖最后由 scutan 于 2007-11-12 21:37 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2007-11-13 15:49 |只看该作者

很好,要仔细品味

很好,要仔细品味  很好,要仔细品味

论坛徽章:
0
3 [报告]
发表于 2007-11-13 17:08 |只看该作者
确实看ldd3时,真的是云里雾里。硬着头皮望下看。多记,回头看,才有一点收获。
多谢楼主能总结,这样对于锁,信号,就能知怎样用.

论坛徽章:
0
4 [报告]
发表于 2007-11-13 17:47 |只看该作者
原帖由 xuxd32 于 2007-11-13 17:08 发表
确实看ldd3时,真的是云里雾里。硬着头皮望下看。多记,回头看,才有一点收获。
多谢楼主能总结,这样对于锁,信号,就能知怎样用.


我以前看的时候也是这样的, 主要是对内核整体没有什么感觉.
建议将LKD与LDD结合起来看效果会好一些. 将这两本看完之后再去看ULK就应该轻松多了.

论坛徽章:
0
5 [报告]
发表于 2007-11-14 15:33 |只看该作者
今天我看了您的code。才google了一些spin_lock和semaphore了解一些,以前,总是在记忆嘛。有些时候,多记也许才能找到出路。以前看的一些,没有您code全面。很有收获。到是希望。有更多的论坛朋友,能想您一样,总结出来就好了。我可能想去华清远见培训一下,不知有没有人知道那里怎么样。当然,以自己为主嘛。可能会系统一点。不然,自学知识太散。什么都知一点,又什么都不能用。呵呵。

论坛徽章:
0
6 [报告]
发表于 2007-11-14 15:34 |只看该作者
LKD我还不知道是什么书呢。我去google一下.

论坛徽章:
0
7 [报告]
发表于 2007-11-14 15:36 |只看该作者
如果有空,那里有电子版下载。请您说一下.

论坛徽章:
0
8 [报告]
发表于 2007-11-14 15:43 |只看该作者
我在论坛上找到了是linux内核开发。我去找找。

论坛徽章:
0
9 [报告]
发表于 2007-11-14 17:31 |只看该作者
原帖由 xuxd32 于 2007-11-14 15:36 发表
如果有空,那里有电子版下载。请您说一下.


LKD是指<Linux Kernel Development>.
我这儿有电子书.

Linux kernel Development.part1.rar

683.59 KB, 下载次数: 818

Linux kernel Development.part2.rar

619.6 KB, 下载次数: 901

论坛徽章:
0
10 [报告]
发表于 2007-11-15 09:04 |只看该作者
谢谢您。成都的朋友。我昨天也在论坛上找了一个,可是,没有办法解压。所以,不行。今天早上来看论坛。您这儿共享了。很高兴哟。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP