linux 字符设备驱动测试,一个简单字符型设备驱动及其测试

驱动对一些人来说很难,而对一些人来说很容易。窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间。对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要的——不过,有许多现在的“模板”可供参考,总算不用白手起家。

本文所述的仅是一个独立的、简单的字符型设备驱动。最简单的字符设备当属经典的“Hello World”。而这个比它复杂一点:在用户空间写一字符串到内核空间,再将其写到用户空间。从应用层角度来看,先写一字符串到内核,再读这个字符串。

先说测试程序,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26#include

#include

#include

#include

#define device "/dev/bar"

int main(void)

{

int fd;

char buf[] = "Late Lee";

char buf2[30]={0};

int len;

fd = open(device, O_RDWR);

if (fd < 0)

{

perror("Open device faile!");

return -1;

}

len = write(fd, buf, sizeof(buf));

printf("buf: %s %d/n", buf, len);

len = read(fd, buf2, 25); // 由此指定读取数据,可大可小,但是驱动只读取这个指定的(大者读实际值),并返回

printf("buf2: %s %d/n", buf2, len);

close(fd);

return 0;

}

代码很简单,使用open打开设备,调用write将buf的东西写到由fd指定的设备(文件)中。之后再使用read读到buf2中,打印字符串,最后关闭设备。就这么简单。

实际运行结果如下:

1

2

3# ./a.out

buf: Late Lee 9

buf2: The voice from hell: Late 25

其中“The voice from hell:”仅仅表明这个字符来自驱动,无实际意义。

驱动程序也很简单,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164/*************************************************************************

Late Lee from http://www.latelee.org

简单的字符型设备驱动

从应用层获取一数据,再复制到应用层(在前面添加字符串)。

注册设备号及设备号的几个宏,均系ldd3例子scull。

何处释放data更好?看ldd3,似乎exit中更好。

2011-04-29 & 2011-05-06

*************************************************************************/

#include

#include        /**< printk() */

#include

#include                 /**< cdev_* */

#include

#include         /**< copy_*_user */

#include         /**< size_t */

#include         /**< error codes */

#include

#ifdef DEBUG /* define it in Makefile or somewhere */

/* KERN_INFO */

#define debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__)

#else

#define debug(fmt, ...)

#endif

#define DEV_NAME "foo"

//#define HAVE_MAJOR

#ifdef HAVE_MAJOR

#define DEVICE_MAJOR 248

#else        /* auto alloc */

#define DEVICE_MAJOR 0

#endif /* HAVE_MAJOR */

#define FOO_NR_DEVS 1

struct cdev foo_cdev;

int foo_major = DEVICE_MAJOR;

int foo_minor = 0;

int foo_nr_devs = FOO_NR_DEVS;

dev_t devno;

char *data;

static int foo_open(struct inode *inode, struct file *filp)

{

debug("in %s()/n", __func__);

return 0;

}

static int foo_release(struct inode *inode, struct file *filp)

{

debug("in %s()/n", __func__);

//kfree(data);

//data = NULL;

return 0;

}

static ssize_t foo_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)

{

int len;

char *tmp;

if (count < 0)

return -EINVAL;

tmp = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL); // ?? here

sprintf(tmp, "The voice from hell: %s", data);

len = strlen(tmp);

if (len < count)

count = len;

if ( copy_to_user(buf, tmp, count) )

return -EFAULT;

debug("in %s() tmp: %s/n", __func__, tmp);

debug("in %s() buf: %s/n", __func__, buf);

debug("in %s() data: %s/n", __func__, data);

kfree(tmp);

return count;

}

static ssize_t foo_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)

{

if (count < 0)

return -EINVAL;

data = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL);

if (data == NULL)

return -ENOMEM;

if (copy_from_user(data, buf, count+1))

return -EFAULT;

debug("in %s() buff: %s/n", __func__, buf);

debug("in %s() data: %s/n", __func__, data);

return count;

}

static int foo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

switch (cmd)

{

default:

return -EINVAL;

}

return 0;

}

static struct file_operations foo_fops = {

.owner   = THIS_MODULE,

.open    = foo_open,

.release = foo_release,

.read    = foo_read,

.write   = foo_write,

.ioctl   = foo_ioctl,

};

static int __init foo_init(void)

{

int ret = -1;

cdev_init(&foo_cdev, &foo_fops);

foo_cdev.owner = THIS_MODULE;

/* register to who? */

if (foo_major)

{

devno = MKDEV(foo_major, foo_minor);

ret = register_chrdev_region(devno, foo_nr_devs, DEV_NAME);

}

else

{

ret = alloc_chrdev_region(&devno, foo_minor, foo_nr_devs, DEV_NAME); /* get devno */

foo_major = MAJOR(devno); /* get major */

}

if (ret < 0)

{

debug(" %s can't get major %d/n", DEV_NAME, foo_major);

return -EINVAL;

}

ret = cdev_add(&foo_cdev, devno, 1);

if (ret < 0)

{

debug(" %s cdev_add failure!/n", DEV_NAME);

return -EINVAL;

}

debug("%s init ok!/n", DEV_NAME);

return 0;

}

static void __exit foo_exit(void)

{

if (data != NULL)

kfree(data);

unregister_chrdev_region(devno, 1);

cdev_del(&foo_cdev);

debug("%s exit ok!/n", DEV_NAME);

}

module_init(foo_init);

module_exit(foo_exit);

MODULE_LICENSE("Dual BSD/GPL");

MODULE_AUTHOR("Chiangchin Li");

其中最后两个函数foo_init和foo_exit分别在加载驱动和卸载驱动时执行。如果驱动程序直接编入内核,可以在启动信息中看到,如果是单独加载,在执行insmod时执行,当然,在何处显示与printk打印级别有关系。

我们的驱动中使用KERN_DEBUG,因此可以使用dmesg查看——注意,由于insmod、rmmod等等需要使用root权限,文中统一使用root,shell提示符为“#”。

1

2

3

4# insmod GotoHell.ko

# dmesg | tail

……

foo init ok!

当执行应用层的测试程序时,dmesg的显示如下:

1

2

3

4

5

6

7

8

9

10

11

12

13# ./a.out

buf: Late Lee 9

buf2: The voice from hell: Late 25

# dmesg | tail

……

foo init ok!

in foo_open()

in foo_write() buff: Late Lee

in foo_write() data: Late Lee

in foo_read() tmp: The voice from hell: Late Lee

in foo_read() buf: The voice from hell: Late

in foo_read() data: Late Lee

in foo_release()

可以看到,当执行close时,驱动中执行foo_release。当卸载模块时,dmesg显示如下:

1

2

3

4

5

6

7

8

9

10

11

12# rmmod GotoHell.ko

# dmesg | tail

……

foo init ok!

in foo_open()

in foo_write() buff: Late Lee

in foo_write() data: Late Lee

in foo_read() tmp: The voice from hell: Late Lee

in foo_read() buf: The voice from hell: Late

in foo_read() data: Late Lee

in foo_release()

foo exit ok!

对照测试程序及驱动显示的调试信息,可以看到应用层调用的如open、write、read、close等等系统调用,在驱动中均在对应的函数,它正是通过如下结构体来实现的:

1

2

3

4

5

6

7

8static struct file_operations foo_fops = {

.owner   = THIS_MODULE,

.open    = foo_open,

.release = foo_release,

.read    = foo_read,

.write   = foo_write,

.ioctl   = foo_ioctl,

};

由于程序比较简单,不作注释和解释,一切尽在代码中。本文中的“模块”、“驱动”在某种意义上是同义词。

本文完整的“工程”代码可以在下面地址下载,为确保下载的东西的确是本文所提及的代码,最好确认一下它的md5和:deb16fcfbdc5e7f136ae988a0a26aad3

工程:简单字符型设备驱动例子-来自latelee.org

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值