📄 x571.html
字号:
><P
><TT
CLASS="VARNAME"
>MOD_DEC_USE_COUNT</TT
>: Decrement the use count.</P
></LI
><LI
><P
><TT
CLASS="VARNAME"
>MOD_IN_USE</TT
>: Display the use count.</P
></LI
></UL
><P
>保持该计数器时刻精确是非常重要的;如果你丢失了正确的计数,你将无法卸载模块,那就只有重启了。不过这种情况在今后编写内核模块时也是无法避免的。</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN685"
></A
>4.1.5. chardev.c</H2
><P
>下面的代码示范了一个叫做 <TT
CLASS="FILENAME"
>chardev </TT
>的字符设备。你可以用 <TT
CLASS="FILENAME"
>cat </TT
>输出该设备文件的内容(或用别的程序打开它)时,驱动模块会将该设备文件被读取的次数显示。目前对设备文件的写操作还不被支持(像
<B
CLASS="COMMAND"
>echo "hi" >
/dev/hello </B
>),但会捕捉这些操作并且告诉用户该操作不被支持。不要担心我们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入数据并输出我们已经接收了数据的信息。</P
><DIV
CLASS="EXAMPLE"
><A
NAME="AEN692"
></A
><P
><B
>Example 4-1. chardev.c</B
></P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>/* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/
#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS)
#include <linux/modversions.h>
#define MODVERSIONS
#endif
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */
/* Prototypes - this would normally go in a .h file
*/
int init_module(void);
void cleanup_module(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 */
access to the device */
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
*/
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk ("Registering the character device failed with %d\n", Major);
return Major;
}
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;
}
void cleanup_module(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;
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 */
/* 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;
}</PRE
></FONT
></TD
></TR
></TABLE
></DIV
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN695"
></A
>4.1.6. 为多个版本的内核编写内核模块</H2
><P
>系统调用,也就是内核提供给进程的接口,基本上是保持不变的。也许会添入新的系统调用,但那些已有的不会被改动。这对于向下兼容是非常重要的。在多数情况下,设备文件是保持不变的。但内核的内部在不同版本之间还是会有区别的。</P
><P
>Linux内核分为稳定版本(版本号中间为偶数)和试验版本(版本号中间为奇数)。试验版本中可以试验各种各样的新而酷的主意,有些会被证实是一个错误,有些在下一版中会被完善。总之,你不能依赖这些版本中的接口(这也是我不在本文档中支持它们的原因,它们更新的太快了)。在稳定版本中,我们可以期望接口保持一致,除了那些修改代码中错误的版本。</P
><P
>如果你要支持多版本的内核,你需要编写为不同内核编译的代码树。可以通过比较宏
<TT
CLASS="VARNAME"
>LINUX_VERSION_CODE</TT
> 和宏 <TT
CLASS="VARNAME"
>KERNEL_VERSION</TT
> 来实现。在版本号为 <TT
CLASS="VARNAME"
>a.b.c</TT
>
的内核中,该宏的值可以为 2^16×a+2^8×b+c 。注意在内核
2.0.35
及之前是没有该宏的。如果你确实要支持很老的内核,你必须自己定义该宏,就像:</P
><DIV
CLASS="EXAMPLE"
><A
NAME="AEN709"
></A
><P
><B
>Example 4-2. some title</B
></P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
> #if LINUX_KERNEL_VERSION >= KERNEL_VERSION(2,2,0)
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif</PRE
></FONT
></TD
></TR
></TABLE
></DIV
><P
>既然它们是宏,你可以用预处理语句 <B
CLASS="COMMAND"
>#ifndef KERNEL_VERSION</B
> 去检测该宏是否被定义,而不是靠内核版本识别。</P
></DIV
></DIV
><H3
CLASS="FOOTNOTES"
>注意</H3
><TABLE
BORDER="0"
CLASS="FOOTNOTES"
WIDTH="100%"
><TR
><TD
ALIGN="LEFT"
VALIGN="TOP"
WIDTH="5%"
><A
NAME="FTN.AEN632"
HREF="x571.html#AEN632"
><SPAN
CLASS="footnote"
>[1]</SPAN
></A
></TD
><TD
ALIGN="LEFT"
VALIGN="TOP"
WIDTH="95%"
><P
>这只是习惯上的。将设备文件放在你的用户目录下是没有问题的。但是当真正提供成熟的驱动模块时,请保证将设备文件放在 <TT
CLASS="FILENAME"
>/dev</TT
> 下。</P
></TD
></TR
></TABLE
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="c569.html"
ACCESSKEY="P"
>返回</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="index.html"
ACCESSKEY="H"
>返回首页</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="c714.html"
ACCESSKEY="N"
>继续</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>字符设备文件</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="c569.html"
ACCESSKEY="U"
>返回本章开始</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>关于 /proc 文件系统</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -