Linux Introduction to Linux/Android System Knowledge--Writing Linux Drivers

Keywords: Linux Attribute sudo Makefile

Preface

Because of its versatility, wide employment, free source code and other reasons, Linux has become popular in recent years, ranging from cloud servers to routers. Knowing that various Linux books recommend a wide range of, "ldd3", "Inside Source Code Situation Analysis", "Deep Understanding of the Linux Kernel" and so on, so that friends with selective difficulties.

Friends of learning Linux must first establish the idea that learning Linux drivers and learning Linux kernels are two different things. The Linux kernels provide various ready-made interfaces for driver developers to use directly. Driver writers only need to learn the rules and concepts of Linux driver operation to write their own driver cases.

It's like when we start writing the first hello world program on Windows, the book just teaches us to type in the following code first:

#include<stdio.h>
void main()
{
    printf("hello world\n");
}

Then click the compile button of Visio studio to compile and run the generated exe file. Just like Linux driver, we can write our first driver code by following its bar and box.

Write the first driver file

#include <linux/init.h>  
#include <linux/sched.h>  
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>    
#include <linux/cdev.h>  
#include <linux/device.h>  
#include <linux/ioctl.h>    

static int __init hello_init(void)  
{  
        printk("hello, init\n");  
        return 0;  
}  

static void __exit hello_exit(void)  
{  
        printk("hello, exit\n");  
}  

module_init(hello_init);  
module_exit(hello_exit);  

MODULE_LICENSE("GPL");  
MODULE_AUTHOR("happybevis");  

Here's our first linux driver code

  • #include \

#define KERN_EMERG      "<0>" /* system is unusable */
#define KERN_ALERT      "<1>" /* action must be taken immediately */
#define KERN_CRIT       "<2>" /* critical conditions */
#define KERN_ERR        "<3>" /* error conditions */
#define KERN_WARNING    "<4>" /* warning conditions */
#define KERN_NOTICE     "<5>" /* normal but significant condition */
#define KERN_INFO       "<6>" /* informational */
#define KERN_DEBUG      "<7>" /* debug-level messages */

The printer level macro can be added to the print function. In this case, it is not added. The system will automatically add the "KERN_WARNING" level to it. So we can also use the printk function as follows: printk(KERN_INFO "Hello, world!\n");

To view the log we printed earlier, we need to use the dmesg command on the command line, which will print the kernel log that was thrown out using the printk function.

Compile driver code

After writing the driver code, how should we compile and use it?

If you are using an operating system such as ubuntu, use the uname command to view the kernel version first

deployer@iZ28v0x9rjtZ:~$ uname  -r
3.2.0-67-generic

Here, the local computer's kernel version is 3.2.0, and the linux based operating system will provide all the compiling environments required for the local kernel module under'/ lib/modules /(kernel version number)/ build'.

If you have friends who want to migrate or have a deeper understanding of the operating principle of the kernel, you can download the source code of the corresponding version of Linux that you want to research or transplant on the Linux official website. These source codes can be compiled and transplanted completely by cross-compiler tools corresponding to the target platform.

The official address of the source code download is Kernel If you want to find the source code of the corresponding version, you can find it in the corresponding folder as follows For example, 3.2 source download.

First, we write our Makefile (compile rule file), the path you can choose, I am placed in the ~/kernel_test/test directory.

  obj-m := hello.o  

  PWD  := $(shell pwd)
  KVER := $(shell uname -r)
  KDIR := /lib/modules/$(filter-out ' ',$(KVER))/build
  all:  
          $(MAKE) -C $(KDIR) M=$(PWD) modules  
  clean:  
          rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions  

Special note: The next sentence of all and clean statements must begin with the tab key, otherwise the Makefile grammar will be wrong.

Copy the hello.c file you wrote earlier into the test folder and call the make command directly to start compiling:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ make
make -C /lib/modules/3.2.0-67-generic/build   M=/home/deployer/kernel_test/test   modules  
make[1]: Entering directory `/usr/src/linux-headers-3.2.0-67-generic'
  Building modules, stage 2.
  MODPOST 0 modules
make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-67-generic'

The compilation was successful.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ls
hello.c  hello.ko  hello.mod.c  hello.mod.o  hello.o  Makefile  modules.order  Module.symvers

Generated a lot of files, what we really need is hello.ko, which is the kernel module, which is our driver module.
The following three commands are required to install and uninstall and view the kernel module:

  1. lsmod: Look at the kernel modules installed in the current system.
  2. insmod: Install the kernel module.
  3. rmmod: Unload the kernel module.
deployer@iZ28v0x9rjtZ:~/kernel_test/test$ lsmod
Module                  Size  Used by
joydev                 17693  0 
xen_kbdfront           12797  0 
fb_sys_fops            12703  0 
sysimgblt              12806  0 
sysfillrect            12901  0 
syscopyarea            12633  0 
usbhid                 47238  0 
hid                    99883  1 usbhid
i2c_piix4              13301  0 
psmouse                98051  0 
serio_raw              13211  0 
mac_hid                13253  0 
lp                     17799  0 
parport                46562  1 lp
xenfs                  18311  1 
floppy                 70207  0 

We started installing the kernel driver:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ insmod hello.ko
insmod: error inserting 'hello.ko': -1 Operation not permitted

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ sudo insmod hello.ko

Because the driver code runs in the kernel space, if a major bug is introduced in the writing process, it may lead to the crash of the Linux kernel and the restart of the operating system. So installing and uninstalling kernel code requires administrator privileges. Interested friends can deliberately write a driver with null pointer code in the virtual machine to see how it works.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ dmesg  |grep -i hello
[2908464.238822] hello, init

As you can see, our hello_init function was successfully called.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ lsmod
Module                  Size  Used by
hello                  12425  0 
joydev                 17693  0 
xen_kbdfront           12797  0 
fb_sys_fops            12703  0 
sysimgblt              12806  0 
sysfillrect            12901  0 
syscopyarea            12633  0 
usbhid                 47238  0 
hid                    99883  1 usbhid
i2c_piix4              13301  0 
psmouse                98051  0 
serio_raw              13211  0 
mac_hid                13253  0 
lp                     17799  0 
parport                46562  1 lp
xenfs                  18311  1 
floppy                 70207  0 

The kernel module has also been successfully loaded.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ sudo rmmod hello.ko
deployer@iZ28v0x9rjtZ:~/kernel_test/test$ dmesg  |grep -i hello
[2908464.238822] hello, init
[2908491.639216] hello, exit

After uninstalling our driver, the hello_exit function was also successfully called. Therefore, we believe that we have a deeper understanding of the call process of the kernel driver.

Interacting with Kernel Drivers

The driver code written earlier essentially prints a few log s without any other substantive operation. As an "available" driver, it is generally necessary to provide an external interface for data interaction and control with the outside world.

In Linux, everything is a file. Kernel drivers can make the operating system establish some file nodes by calling some functions. These file nodes are all ordinary files in the user's eyes. We can transfer data by reading and writing special files and ioctl operation.

deployer@iZ28v0x9rjtZ:~$ ll 
drwxr-xr-x  2 root root          60 Jun 26 00:08 cpu/
crw-------  1 root root      1,  11 Jun 26 00:08 kmsg
-rw-r--r--  1 root     root       156 Jul 30 06:00 run_jobs.log
brw-rw---- 1 root disk 7, 7 Jun 26 00:08 /dev/loop7
...

We can see whether a file is a real file by the ls-l command. We only need to pay attention to the first character: "c" - > character device driver (char); "b" - > block device driver; "d" - > directory; - > file.

We often create file nodes for interaction in the following four ways:

  1. Proc is often used to make simple parameter viewing nodes. Because sys file system functions are very similar to them and sys updates are more flexible, proc has been used less and less recently.
  2. sys is often used to control the operating parameters of the driver.
  3. dev .
  4. debugfs (this method is usually used to provide debug debugging interface, normal driver is not likely to provide, so will skip its explanation, interested friends please learn by yourself)
deployer@iZ28v0x9rjtZ:~$ ls /
bin   dev  home lib lost+found  mnt  proc  run  selinux  sys  usr  vmlinuz
boot  etc  initrd.img  lib64  media opt  root  sbin  srv   tmp  var

Looking at the system root directory, it is easy to see sys, proc, dev three folders, respectively, call the three functions mentioned above to create the file system, the operating system will help us establish the corresponding file nodes in the following three folders.

Establishing dev file node

Make a slight change in the driver file we have written:

#include <linux/init.h>  
#include <linux/sched.h>  
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>    
#include <linux/cdev.h>  
#include <linux/device.h>  
#include <linux/ioctl.h>   
#include <linux/miscdevice.h> 
#include <linux/uaccess.h>

    int hello_open(struct inode *inode, struct file *filp)  
    {  
            printk("hello open!\n");  
            return 0;   
    }  

    int hello_release(struct inode *inode, struct file *filp)  
    {  
            printk("hello release!\n");  
            return 0;  
    }  

    ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)  
    {  
        printk("hello read!\n");  
        return 0;  
    }  

    ssize_t hello_write(struct file *filp, char __user *buf, size_t count, loff_t *fpos)  
    {  
        printk("hello write!\n");  
        return 0;  
    }  

    int hello_ioctl(struct inode *inode, struct file *filp,  
            unsigned int cmd, unsigned long arg)  
    {  
        printk("hello ioctl!\n");  
        printk("cmd:%d arg:%d\n", cmd, arg);  
        return 0;  
    }  


    struct file_operations fops =   
    {  
        .owner      =   THIS_MODULE,  
        .open       =   hello_open,  
        .release    =   hello_release,  
        .write      =   hello_write,  
        .read       =   hello_read,  
        .unlocked_ioctl      =   hello_ioctl  
    };    

    struct miscdevice dev =   
    {  
        .minor  =   MISC_DYNAMIC_MINOR,  
        .fops    =   &fops,  
        .name   =   "hello_device"
    };    

    int setup_hello_device(void)  
    {  

        return misc_register(&dev);  
    }   

    static int __init hello_init(void)  
    {  
            printk("hello, init\n");  
            return setup_hello_device();  
            return 0;  
    }  

    static void __exit hello_exit(void)  
    {  
            printk("hello, exit\n");  
        misc_deregister(&dev);
    }  

    module_init(hello_init);  
    module_exit(hello_exit);  

    MODULE_LICENSE("GPL");  
    MODULE_AUTHOR("happybevis"); 

Next, after making the driver module, install our driver module.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ls /dev/h*
hello_device  hidraw0       hpet 

You can see that the dev directory produces the "hello_device" file node we created.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ dmesg  |grep -i hello
[2980042.371331] hello, init

Next, let's read the driver node content as we read the file.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ sudo cat /dev/hello_device
deployer@iZ28v0x9rjtZ:~/kernel_test/test$ dmesg  |grep -i hello
[2980042.371331] hello, init
[2980380.028918] hello open!
[2980380.028935] hello read!
[2980380.028938] hello release!

Visible cat command will help us do open, read, close three operations. Because we just print a log in the read function of the kernel and don't really transfer data, cat can't get the content.

Next, we make a slight change to return a string to the user (file reader) in the driver read function.

ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)  
       {
          char *hellobuff = "hello world\n" ;
          loff_t position = *pos; 

          if (position >= 15) {
                  count = 0;
                  goto out; 
          }       

          if (count > ( 15 - position ))
                  count = 15 - position;
          position += count; 

          printk("hello read!\n");
          if( copy_to_user( buf, hellobuff + *pos , count ) ){
                  count =  -EFAULT;
                  goto out;
          }       

          *pos = position;

          out:
                  return count;  
          } 

make install again to test our functionality:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ sudo cat /dev/hello_device 
hello world 

The test was successful so that information could be read and written between kernels and users. Data transfer in user space and kernel space requires the use of copy_to_user and copy_from_user functions, the former transfers data to users in read function of driver, and the latter uses write to obtain user's incoming data.

We used the cat command to read the driver data, of course, echo "xxx" > / dev / hello_device can be used to transfer data to the driver. In addition, of course, we can write our own linux applications, which can also deal with driver by calling normal fopen, fread, fwrite, fclose function groups (open, rea, write, close, etc.).

The test procedure is as follows:
#include <fcntl.h>  
#include <stdio.h>  

char temp[64]={0};  
int main( void )  
{  
    int fd,len;  

    fd = open("/dev/hello_device",O_RDWR);  
    if(fd<0)  
    {  
        perror("open fail \n");  
        return ;  
    }   

    len=read(fd,temp,sizeof(temp));  

    printf("len=%d,%s \n",len,temp);  

    close(fd);  
}  
deployer@iZ28v0x9rjtZ:~/kernel_test/test$ gcc test.c  -o test

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ./test 
open fail 
: Permission denied

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ sudo ./test 
len=15,hello world

You can see that our driver content is normally read.

In addition to the file operations of read and write, there are IOCTL operations in linux applications. The specific details can be viewed by using the man ioctl command. We only need to add IOCTL (fd, para, & return buff) into the code to interact with the hello_ioctl function in our driver.

Normally, because we have more customized ioctl para, we usually write in driver as follows:

static long hello_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)  
{  
    int err = 0;  
    int returnbuff = 0 ;
    printk("hello ioctl!\n");  
     printk("cmd:%d arg:%d\n", cmd, arg);  
    switch(cmd)  
    {  
        case PARA1:  
            printk("case: PARA1/n");  
            // do something ... 
            break;  
        case PARA2:  
            printk("case: PARA2/n");  
            err = copy_to_user((int *)arg, &returnbuff, sizeof(int));  
            break;  
        case PARA3:  
            printk("case: PARA3/n");  
            err = copy_from_user(&returnbuff,(int *)arg, sizeof(int));  
            break;  
        default:  
            printk("case: default/n");  
            err = ENOTSUPP;  
            break;  
    }   
    return err;  
}  

So ioctl is more flexible than read write, and the code structure is relatively clear.

Let's briefly summarize that user programs (shell commands or linux applications) can interact with each other by driving the created file nodes in a conventional way of file manipulation. The way of interaction is usually through read, write, ioctl(dev file nodes only support) for data exchange. In this way, kernel s and the real world can cooperate and communicate with each other.

Establishment of proc file node

Due to historical reasons, the use of proc file nodes in the earlier version of kernel and the new version are different and incompatible. Since the old version has been used in very few places, we will study the new usage.

Continue to add proc file node related code on the basis of our driver:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ git diff hello.c
diff --git a/hello.c b/hello.c
index 9fda5e2..8a4d32a 100644
--- a/hello.c
+++ b/hello.c
@@ -8,7 +8,9 @@
 #include <linux/ioctl.h>   
 #include <linux/miscdevice.h> 
 #include <linux/uaccess.h> 
 #include <linux/slab.h>
+#include <linux/proc_fs.h> 
+#include <linux/seq_file.h>

        int hello_open(struct inode *inode, struct file *filp)  
        {  
@@ -60,8 +62,26 @@
            printk("hello ioctl!\n");  
            printk("cmd:%d arg:%d\n", cmd, arg);  
            return 0;  
       }  

+  
+       static int hello_proc_show(struct seq_file *m, void *v)
+       {
+               seq_printf(m, "hello world\n");
+               return 0;
+       }       
+
+       static int hello_proc_open(struct inode *inode, struct file *file)
+       {
+               return single_open(file, hello_proc_show, NULL);
+       }
+
+       static const struct file_operations hello_proc_fops = {
+       .owner          = THIS_MODULE,
+       .open           = hello_proc_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+       }; 

        struct file_operations fops =   
        {  
@@ -82,7 +102,8 @@

        int setup_hello_device(void)  
        {             
+           if (!proc_create("hello_proc", 0, NULL, &hello_proc_fops))
+               return -ENOMEM;     
            return misc_register(&dev);  
        }   

@@ -97,6 +118,7 @@
        {  
                printk("hello, exit\n");  
                misc_deregister(&dev);
+               remove_proc_entry("hello_proc", NULL);
        }  

        module_init(hello_init);  

Compile and install our kernel driver, and if the code is all right, you should see the following nodes grow up uuuuuuuuuuu

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ll /proc/hello_proc 
-r--r--r-- 1 root root 0 Jul 30 15:02 /proc/hello_proc

We read the node information as follows:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ cat /proc/hello_proc 
hello world

It should be pointed out that seq-related operations are provided by the kernel in order to make proc file nodes highlight more content. The old version of proc's operation interface is easy to exchange more data than copy_to_user can support because of improper use by developers (it should be a page size if you remember correctly). For users of proc, direct application of the above framework can ensure the security of use.

In addition, it should be noted that the. release = seq_release domain in the proc file operation structure must not be omitted, otherwise, every time the proc node is opened, some memory leaks will occur, although the leakage is very small, the accumulated memory will explode sooner or later.

Establishing sys File Node

With the previous experience, it is easy to think that the operation mode of sys fs FS file nodes is much the same. Generally, we can monitor the status of driver and dynamically modify the behavior of driver by cat and echo operation of sys fs node to view or change some global variables of driver.

Next, let's start with a folder named hello_sysfs_dir in the sys directory, in which we build a node hello_node that can be read and written.
Say no more and go.

In the completed proc driver On this basis, we add memory sys fs Operation node
diff --git a/hello.c b/hello.c
index 8a4d32a..f61fafe 100644
--- a/hello.c
+++ b/hello.c
@@ -11,6 +11,7 @@
 #include <linux/slab.h>
 #include <linux/proc_fs.h> 
 #include <linux/seq_file.h>
+#include <linux/kobject.h>

        int hello_open(struct inode *inode, struct file *filp)  
        {  
@@ -63,6 +64,27 @@
            printk("cmd:%d arg:%d\n", cmd, arg);  
            return 0;  
        }
+
+       char buff[512] = {0} ;
+       int in_count = 0;
+
+       static ssize_t hello_sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf
+       {
+                //return sprintf(buf, "%s\n", "hello sysfs read\n");
+                return sprintf(buf, "[hello sysfs write] user data: length=0x%X,buff=%s\n",in_count
+       }
+
+       static ssize_t hello_sysfs_store(struct kobject *kobj, struct kobj_attribute *attr, const ch
+       {
+        printk("[hello sysfs write] user data: length=0x%X,buff=%s\n",count,buf);
+       in_count = count ;
+       strncpy(buff, buf , 512); 
+       if(count)
+               return count;
+        else
+               return 1 ;
+       }
+

        static int hello_proc_show(struct seq_file *m, void *v)
        {
@@ -98,12 +120,41 @@
            .minor  =   MISC_DYNAMIC_MINOR,  
            .fops    =   &fops,  
            .name   =   "hello_device"  
      }; 
+
+static struct kobj_attribute hello_attr = 
+               __ATTR(hello_node, 0666, hello_sysfs_show, hello_sysfs_store);
+ 
+static struct attribute *attrs [] = {
+               &hello_attr.attr,
+               NULL,
+       };  
+
+static struct attribute_group hello_attr_group = {
+       .attrs = attrs,
+       };

+struct kobject *dir = NULL;
+
+
        int setup_hello_device(void)  
       {   
+        int retval = 0 ; 
+         //--------proc fs part-------------- 
          if (!proc_create("hello_proc", 0, NULL, &hello_proc_fops))
                return -ENOMEM;     
+       
+       //--------sys fs part ----------------
+           dir = kobject_create_and_add("hello_sysfs_dir", NULL);
+            if (!dir)
+                 return -ENOSYS;
+               
+          retval = sysfs_create_group(dir, &hello_attr_group);
+            if (retval)
+               kobject_put(dir);
+           
+
+       //---------create a device(dev fs) part------------
            return misc_register(&dev);  
        }   

@@ -119,6 +170,7 @@
                printk("hello, exit\n");  
                misc_deregister(&dev);
                remove_proc_entry("hello_proc", NULL);
+               kobject_put(dir);
        }  

        module_init(hello_init); 

In this way, the string we write to the node can be read out and verified by cat.

Immediately compile and install the test results:

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ll /sys/
total 4
drwxr-xr-x 14 root root    0 Jul 30  2017 ./
drwxr-xr-x 23 root root 4096 Mar  9  2015 ../
drwxr-xr-x  2 root root    0 Jul 30  2017 block/
drwxr-xr-x 21 root root    0 Jul 30  2017 bus/
drwxr-xr-x 44 root root    0 Jul 30  2017 class/
drwxr-xr-x  4 root root    0 Jul 30  2017 dev/
drwxr-xr-x 18 root root    0 Jul 30  2017 devices/
drwxr-xr-x  4 root root    0 Jul 30 15:48 firmware/
drwxr-xr-x  6 root root    0 Jul 30 15:48 fs/
drwxr-xr-x  2 root root    0 Jul 30 15:59 hello_sysfs_dir/
...
deployer@iZ28v0x9rjtZ:~/kernel_test/test$ ll /sys/hello_sysfs_dir/
total 0
drwxr-xr-x  2 root root    0 Jul 30 15:59 ./
drwxr-xr-x 14 root root    0 Jul 30  2017 ../
-rw-rw-rw-  1 root root 4096 Jul 30 15:59 hello_node

The file nodes of sysfs have risen perfectly, so try our functions as soon as possible.

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ echo "how are you" >/sys/hello_sysfs_dir/hello_node 

deployer@iZ28v0x9rjtZ:~/kernel_test/test$ cat /sys/hello_sysfs_dir/hello_node 
[hello sysfs write] user data: length=0xC,buff=how are you

The result is as expected, with great success!

Summary

The main way for Linux drivers and users to deal with each other is to establish various file nodes (netlink etc.). This paper takes three ways of creating file nodes as the main line, so that you can have a more intuitive understanding of the idea that everything in Linux is a file. All the details of this article are revised step by step and the results are returned, so I suggest that if you have the conditions, you must follow the steps of the article completely once, and see more is better than doing it in person to comprehend more.

driver is a university subject. Detailed knowledge details need a book to finish. The space is limited. Only in this paper, many details have not been elaborated on purpose. Readers are welcome to discuss and improve if they have doubts or suggestions.

Posted by irishprogrammin on Tue, 01 Jan 2019 22:06:08 -0800