The first driver in Linux

Source: Internet
Author: User

Because the articles and experiments written in the Ubuntu environment do not have a tool that is easy to use in Linux, so there is not much screenshots, but hope haihan, but the descriptions are all in place.

 

I have been developing applications for a long time, thinking that the driver developer is so powerful that only a cool man can take this step. With the accumulation of knowledge, I found that this is not the case, driving development is not as special as you might think. As the saying goes, there is a specialization in the industry. Developers only use different tools and have different fields and products. As long as you can make a good product, you are a "cool man ".

 

The main thread of the systematic driver learning is "Linux device driver development details". I have read this book before. At first I felt a little obscure, but after reading the books on two kernels, it's easy to read it back. I read several chapters in one breath (the first few chapters are introductory articles ). So here we recommend two kernel books:

Linux kernel design and implementation: I am reading the second version. It is very good and easy to understand. I want to introduce the kernel principles and implementation mechanisms in a wide range and the depth is not enough, so it is better to read this book together.

 

Understandinglinux kernel-a deep understanding of the Linux kernel. The third edition of this book is based on the 2.6 kernel and published in. I see the original English version, so I can see that the speed is quite full, but I just got connected to "Linux device driver development details". I just saw synchronous Asynchronization in "Understanding Linux kernel, it should be in the fifth chapter, and "Linux device driver development details" is also described in Chapter 7. In this way, there may be so many mechanisms: locks, read/write locks, sequential locks, semaphores, read/write semaphores ...... If you read "Linux kernel design and implementation" before, you will not feel panic at least. In fact, learning new knowledge is like this, it is easy to understand things that are difficult to understand.

 

Okay. Let's start learning the first driver. The instance comes from the Linux device driver development details. Here we create a virtual character device globalmem, which is the memory area of a kernel space, to transfer information between the kernel space and the user space. (Indeed, I cannot give a better example as the first one, but I believe that, even in this example, I typed it by letter or letter, I did not directly use the source code of Sui Shu, mainly to view my comments, the problems I encountered and the whole process of solving it, the winners found a way, and the losers made excuses! Haha)

 

Don't talk nonsense, go to the Code, don't worry, read comments...

Below is the driver's source code, with a detailed note:

Globalmem. c

# Include <Linux/module. h> # include <Linux/Fs. h> # include <Linux/types. h> # include <Linux/errno. h> # include <Linux/sched. h> # include <Linux/init. h> # include <Linux/cdev. h> # include <ASM/Io. h> # include <ASM/system. h> # include <ASM/uaccess. h> # define globalmem_size 0x1000/* 4 K space */# define mem_clear 0x1/* clear global memory */# define globalmem_major 250/* default master device Number */ static int globalmem_major = globalmem_major; /* use the object-oriented idea to re-Execute cdev Encapsulation to facilitate our operations */struct globalmem_dev {struct cdev; unsigned char mem [globalmem_size] ;}; struct globalmem_dev * globalmem_devp; /* declare a global device struct * // * used to register with the file_operations structure open */INT globalmem_open (struct inode * inode, struct file * filp) {filp-> private_data = globalmem_devp;/* when there are multiple similar devices, it is necessary and convenient to access with private variables */return 0 ;} /* used to register with the file_operations structure release */INT globalmem_release (struct inode * inode, struct fil E * filp) {return 0;}/* is used to register IOCTL */static int globalmem_ioctl (struct inode * inodep, struct file * filp, unsigned int cmd, unsigned long Arg) {struct globalmem_dev * Dev = filp-> private_data;/* get the device struct pointer from private data */switch (CMD) {Case mem_clear: memset (Dev-> MEM, 0, globalmem_size); printk (kern_info "globalmem is set to zero \ n"); break; default: Return-einval;} return 0 ;} /* used to register with file_operation In the s structure, read */static ssize_t globalmem_read (struct file * filp, char _ User * Buf, size_t size, loff_t * PPOs) {unsigned long P = * PPOs; /* Get the current global memory */unsigned int COUNT = size;/* Get the size of the data to be read */int ret = 0; /* used to record the return value */struct globalmem_dev * Dev = filp-> private_data;/* get the device struct pointer from the private variable * // * analyze and obtain the valid read length, is to see whether the length to be read is valid */If (P> = globalmem_size) return 0; If (count> GLOBALMEM_SIZE-p)/* if you want to read more than the remaining, only give the amount it can read */cou Nt = GLOBALMEM_SIZE-p;/* after everything is ready, start to read to the user space, here the read refers to the user space read, that is, from the kernel copy to the user space, * Because the kernel is omnipotent and has all permissions, the initiative lies in it. It does not mean that users can read what they want, because the space is allocated in the kernel state */If (copy_to_user (BUF, (void *) (Dev-> mem + p), count )) /* copy count data from the offset P of mem to the zone specified by BUF */ret =-efault;/* if the copy fails,-efault */else {/* is returned, returns the re-copied data volume and recalculates the offset */* PPOs + = count; ret = count; printk (kern_info "read % u bytes (s) from % lu \ n ", count, P);} return ret;}/* is used to register in the file_operations structure W Rite */static ssize_t globalmem_write (struct file * filp, char _ User * Buf, size_t size, loff_t * PPOs) {unsigned long P = * PPOs; /* Get the current global memory */unsigned int COUNT = size;/* Get the size of the data to be read */int ret = 0; /* used to record the return value */struct globalmem_dev * Dev = filp-> private_data;/* get the device struct pointer from the private variable * // * analyze and obtain the valid write length, is to see whether the length to be written is valid */If (P> = globalmem_size) return 0; If (count> GLOBALMEM_SIZE-p)/* if you want to read more than the remaining, only give it the amount that can be read */COUNT = G LOBALMEM_SIZE-p;/* after everything is ready, start to write to the shared space, here the write refers to the copy from the user space to the kernel, * because the kernel is omnipotent with all permissions, the initiative lies in it. It does not mean that users can write data as they want, because the space is allocated in the kernel state */If (copy_from_user (Dev-> mem + P, Buf, count)/* copy count data (from the Buf area) to the offset P of MEM */ret =-efault;/* Copy failed, return-efault */else {/* copied successfully. Return the re-copied data volume and the re-calculated offset */* PPOs + = count; ret = count; printk (kern_info "read % u bytes (s) from % lu \ n", Count, P);} return ret;}/* seek file locating function */static loff_t globalmem_llse Ek (struct file * filp, loff_t offset, int orig) {loff_t ret = 0; Switch (orig) {Case 0: /* relative to the start position of the file */If (offset <0) {ret =-einval; break;} If (unsigned INT) Offset> globalmem_size) {ret =-einval; break;} filp-> f_pos = (unsigned INT) offset; ret = filp-> f_pos; break; Case 1: /* relative to the current file location */If (filp-> f_pos + offset)> globalmem_size) {ret =-einval; break;} If (filp-> f_pos + offset) <0) {ret =-einval; break;} f ILP-> f_pos + = offset; ret = filp-> f_pos; break; default: ret =-einval; break;} return ret ;} /* file operation struct */static const struct file_operations globalmem_fops = {. owner = this_module ,. llseek = globalmem_llseek ,. read = globalmem_read ,. write = globalmem_write ,. IOCTL = globalmem_ioctl ,. open = globalmem_open ,. release = globalmem_release,};/* function for device initialization */static void globalmem_setup_cdev (struct globalmem_d Ev * Dev, int index) {int err, devno = mkdev (globalmem_major, index);/* mkdev macro to generate a device number with 12 master device numbers, 20 digits from the device Number */cdev_init (& Dev-> cdev, & globalmem_fops); Dev-> cdev. owner = this_module; err = cdev_add (& Dev-> cdev, devno, 1); If (ERR) printk (kern_notice "error % d adding globalmem % d", err, index );} /*************************** module-related functions ********** * ****************** // * to facilitate development and debugging, we recommend that you first define the initialization and uninstallation functions related to these modules. You can only implement an empty function. Compile (make) The function once, which is very helpful for the development of * // * driver Loading Function */INT globalmem_init (void) {int result; dev_t devno = mkdev (globalmem_major, 0);/* apply for the device Number */If (globalmem_major) Result = register_chrdev_region (devno, 1, "globalmem "); else {/* dynamic application */result = alloc_chrdev_region (& devno, "globalmem"); globalmem_major = major (devno);} If (result <0) return result; /* apply for the global shared memory used by the device struct dynamically */globalmem_devp = kmalloc (sizeof (struct globalm Em_dev), gfp_kernel); If (! Globalmem_devp) {result =-enomem; goto fail_malloc;} memset (globalmem_devp, 0, sizeof (struct globalmem_dev); terminate (globalmem_devp, 0); Return 0; fail_malloc: /* restore the site after the allocation fails, and release the registered device */unregister_chrdev_region (devno, 1);/* release the device Number */return result ;} /* driver uninstall function */void globalmem_exit (void) {cdev_del (& globalmem_devp-> cdev);/* cancel the device */kfree (globalmem_devp ); /* release the allocated memory, so it is not difficult to take advantage of it. */unregister_chrdev_region (mkdev (globalmem_major, 0), 1 ); /* release the device Number */} module_author ("Jun <junlinsong@gmail.com>"); module_license ("dual BSD/GPL"); module_init (globalmem_init); module_exit (globalmem_exit );

The makefile method is the same as the makefile method of the helloworld module in the previous article. Just change the module name. Here is:

OBJ-M + = globalmem. O # XXX. O corresponds to your XXX. C is also your module name all: Make-C/usr/src/linux-headers-2.6.32-27-generic M = $ (shell PWD) Modules # Here you get system information through the uname-R command, at the same time, the path of the kernel source code tree is assembled; # PWD gets the current folder, which requires you to go to the source code directory during make. Clean: Make-C/usr/src/linux-headers-2.6.32-27-generic M = $ (shell PWD) clean # Same principle

 

Start compilation after preparation, that is, make and then OK;

 

Make encountered the following problem:

Make: Nothing to be done for 'all '.

When makeclearn is used:

Make: Nothing to be done for 'clearn '.

 

All the information on the Internet is compiled, but the source file is not modified, so no re-compilation is performed. (I know this is also the reason for the existence of make. Its function is like this, avoid recompilation of unmodified files), but it is shown that it does not exist. I am a little confused, and there is no other error message during compilation. In order to do the test, I tried to compile the previous helloworld module and finally reported an error. The problem is that make-C/usr/src/Linux-headers-$ (shell uname-R) M = $ (Shell
PWD) modules, which dynamically obtains the source code path of the current kernel through the uname of the shell command and connects to the compilation driver. The kernel I downloaded and built is 2.6.32-30-generic, and the system loads (that is, the kernel version obtained by the uname-R command): 2.6.30-27-generic, I have manually deleted the kernel source code tree of this version, so my source code path is inconsistent with that assembled through uname-R. Therefore, I manually changed the commands in makefile:

Make-C/usr/src/linux-headers-2.6.32-30-generic M = $ (shell PWD) modules, specify the path directly (this reduces maintainability, but our project is too small to ignore this problem ). OK, try again, make passed, and the files you want to see are compiled in the directory. They look so cute.

 

The module is just a means to dynamically load your code to the kernel, this explains why the MAKEFILE file of my driver module is exactly the same as the MAKEFILE file of the helloworld module (indeed, the module name is different, you know what I mean ), therefore, for the driver, the module is a boat that carries the code to the sea area where the kernel is located. as a carrier, the driver code is carried to the kernel space, so that when you call some system operations in the user space, the OS can find the corresponding code to complete your request.

 

Okay. After compilation, load and test it.

 

$ Insmod globalmem. Ko # "add sudo to Ubuntu if necessary, because I have enabled the Ubuntu Su"

 

Haha, it's wrong again. It seems that we are going to grow up again. We need to be kind to none of the failures you encounter. After all, she is the mother of success.

 

Loading error:

Insmod: Error inserting 'globalmem. ko':-1 invalid module format

 

Because the current kernel installed by our system is inconsistent with the kernel version compiled by the module, I have to install a newer kernel in the built kernel source code tree to the system. The method for installing the new kernel here can be used for reference in this blog post (Haha, I am more and more fond of Western Mail students, if you want to take a postgraduate entrance exam or recruitment, Western Mail is a great tool for Linux. Amount, advertising, huh): http://edsionte.com/techblog/archives/3289/comment-page-1#comment-2350

# Append description

The comments on the third floor woke me up. This is a program compilation problem. Here it should be a built-in defense problem, it will check that the character string of the version number recorded by your module is inconsistent with that of the currently running kernel module, which is theoretically correct.

Invalid module format may be caused by the following reasons:

    • The source code version of the used kernel is different from the current kernel version;
    • Different compilation targets, such as i686 compiling and i386 compiling;
    • Different compiler versions are used;
    • The current kernel is not compiled by yourself.
  • Here we recommend that you read this article to understand: the first driver helloworld
    Module loading insmod "invalid module format" problem solved

 

Everything is ready. After the kernel of the corresponding version is loaded, restart and enter the corresponding kernel version. Re-insert our module and try again.

 

$ Insmod globalmem. Ko

 

Welldone, We 've ve already made it. Okay, let's test it in the user space.

The driver is for the device (although the device here is not real, it is just a piece of memory ), linux devices are abstracted into files (after all, Unix first evolved from a file system, that is, this feat, making driver development much easier, unified abstraction brings great convenience ). So we need to create this device file.

$ Mknod/dev/globalmem c 250 0 # create a device file named globalmem with the master device number being 0 for the second time to go to/dev.

 

Then you can start the test:

$ Echo "Hello Jun">/dev/globalmem # write "Hello Jun" to the device

Tip:

Bash:/dev/globalmem: Permission denied

Insufficient permissions. ls-l found:

CrW-r -- 1 Root 250, 0 Oct 20 :47/dev/globalmem

You can either use sudo for ECHO or grant the File Permission to you. Haha

 

$ CAT/dev/globalmem # display the content of the device file

 

Shown as follows:

 

 

Root @ Jun-desktop:/home/Jun/driver/ch6 # Cat/dev/globalmem

Hellojun

Okay, you're done !!!

 

Note:

1. If the driver is used, it is recommended that it be convenient in the native Linux environment.

2. We recommend a C/C ++ ide -- code: blocks, which is a very useful integrated development environment. It is just the first time I liked it, but it is slightly different from the scim input method, you must enter Chinese Characters During the annotation process. If Chinese characters cannot be entered during the deletion process, the Chinese characters must be changed to English and then back to Chinese to continue.

In addition, it has the statement Association function. Of course, it only supports user space c-function library Association and does not support functions or structures in the kernel. I personally feel that it is more efficient than vi.

3. Echo is "!" . You can see in the screenshot that you want to output! Escape characters are also required.

# Append explanation: this is mainly because! Is a shell command. "" The shell command in double quotation marks will be parsed. \! is available here \! Escape, or use ''single quotes. Single quotes are not parsed ...... Shell programming has no foundation, so you have to strengthen it!

4. From the article, we can see that the author is really stupid. Every time I record it, I will encounter so many seemingly mentally retarded problems. Fortunately, I found a solution, detailed and realistic records are in line with the characteristics of my blog.

5. The environment for writing and compiling the driver is not the virtual machine environment introduced in the previous article. It is ubuntu10.04 on the local hard disk. It may take some time on my machine, forget the kernel version to highlight the many problems encountered above, unlikely (you will not encounter a similar issue of loading and compiling the kernel during the development process ); however, you may encounter it elsewhere. Never say you have never seen it before, but you are helpless.

6. I posted a blog about the record process of the first driver experiment in advance. The driver-related knowledge is not introduced yet. It may not be very similar to the previous knowledge. I have to thank you for your time. However, I hope that readers and I will continue to accumulate knowledge about the kernel system, which is very easy to understand.

[Download the doc and source code in this article]

.

.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.