Kernel module programming (7): debug by reading the proc file

Source: Internet
Author: User

This article is also called "Linux Device Drivers", the fourth chapter of LDD3's Debuging Techniques's reading notes, but we are not limited to this content.

  In linux, for example, to read the CPU, you can use cat/proc/cpuinfo. Through this, we can obtain the CPU by reading files in the program. This high-capacity and high-performance service is very commonly used, for example, when the cpu is greater than 60%, we will reject all business requests until the cpu is restored to 40%. We can implement multi-level CPU overload protection based on this, which is very common in basic telecommunication systems. Because the method of reading the CPU is used, it can also be obtained by JAVA programs. If we develop a long-term stable business system, overload protection should be considered.
[Programming philosophy 1: CPU overload protection]

/Proc is a special file system, which is created through software and provided by the kernel. We previously used printk to provide debugging information. Although the macro definition was used to determine whether to provide the printk or some part of the printk during compilation, once the module was loaded, the printk was finalized. If not, do not write the/var/log/messages file. The/proc file system can meet this requirement. We can obtain information as needed. However, this operation is risky. For example, if we detach a module while reading the module, one or two different modules use the same/proc/filename for information output. The author in the book gave a uugly example (the author's original saying: somewhat uugly). It took me a lot of time to get through, examples (you can search for ldd3 examples via Google to get the example of a CD-book) is misleading, and I have made a lot of circles.

The following function is used to create and delete files under/proc. The file name is scullmems, which are called when the module is loaded and detached.

Create_proc_read_entry

("Scullmem"/* file name */

,

0/* default mode */

,

NULL/* parent dir, NULL indicates that the default path is/proc */

,

Scull_read_procmem/* function called when reading the proc file */

,

NULL/* client data */

);

Remove_proc_entry

("Scullmem", NULL/* parent dir */

);

The key is how to write scull_read_procmem. Its structure isInt (* read_proc) (char * page, char ** start, off_t offset, int count, int * eof, void * data );
The returned values are char ** start and int * eof, and void * data is the parameter carried during file creation. The kernel does not process the data, but is passed during the start call, we can use it to carry some information. Others are input values.

If the information returned by the kernel module is greater than the maximum space that can be stored, cat/proc/scullmems triggers multiple calls to the function. This is not only troublesome, but also requires caution, LDD3 recommends using self_file (not visible yet) instead of this method, which may be the reason why the author laughed at uugly. However, I think what really uugly is the sculld example in example. I 've been confused for a long time by converting buff forward content into backward data storage. In the scull example, the page size of my machine is 1024 bytes. All the information cannot be transferred at one time and needs to be transferred multiple times. However, the processing description of this part is not detailed. The following is my practical experience. It may not be very accurate but it works. Let's take a look at an experiment. Instead of processing the complicated scull array structure

But I want to output some information and add the printk for tracking:

Static int my_index = 0, total_index = 10;

Int scull_read_procmem (char * buf, char ** start, off_t offset, int count, int * eof, void * data)

{

Int I, len = 0;

Int limit = count-80;

Printk ("================ buf % p * start % p offset % li len % d eof % d count % I limit % I/ n ",

Buf, * start, offset, len, * eof, count, limit );

If (my_index> = total_index ){

Printk ("= _ = return len = % I limit = % d eof = % d/n", len, limit, * eof );

Return 0;

}
/* If (Offset> count/2 ){

* Start = Buf;

} Else {

Buf = BUF + offset;

* Start = Buf;


Limit = count-offset-80;

}*/

If (Offset> 0)


* Start = Buf;

Printk ("=== Buf % P * start % p offset % Li Len % d EOF % d count % I limit % I/N ",

Buf, * Start, offset, Len, * EOF, Count, limit );

For (I = my_index; I <total_index & Len <= limit; I ++ ){

Printk ("% 03d Len = % d 12345678901234567890123456789901234567890/N", I, Len );

Len + = sprintf (BUF + Len, "% 03d Len = % d 12345678901234567890123456789901234567890/N ",

I, Len );

My_index ++;

}

* EOF = 1;

Printk ("= return Len = % I limit = % d EOF = % d Index = % d/N", Len, limit, * EOF, my_index );

Return Len;

}

The first parameter char * buf indicates the buf position of the output information. count indicates the buf space size, which is usually allocated based on the page. In my machine, count = 1024.

One of the keys is that we need to tell the user whether to continue reading this time. This is not clear in LDD3. This is related to the returned value and the returned parameter * eof. * The default input value of eof is 0. If we need to tell the user that the information has not been read, we still need to read it further. We set * eof to 1 and return the length of the read content. If you can read the information this time, that is, return the valid length, the next call will be made regardless of the eof value in my experiment. If we set * eof = 0 and return 0, no further information is available. In our experiment, there are the following rules:

  • The returned value is 0 for two consecutive times and is no longer read.

  • If the value of * EOF is different from the value returned in the previous validity period (and the value cannot be 0 in the previous period), the return value is 0 and is no longer read.
  • If the read is valid, Set * EOF to a positive integer.

 

Offset has some meanings. I started to understand it as the offset in buf, But we output information at once. If the last output occupies the buf space, we cannot have enough space for new information. For multiple outputs, offset is actually the length of the content that has been effectively read. It is a cumulative value. The offset does not refer to

Offset refers to the offset of the output information, that is, the length of the read completed.

In the above example, to ensure that the data is stored in a valid Buf space, we assume that the output of each row will not exceed 80 bytes. Therefore, we must judge each time we write (sprintf, whether there is sufficient space.

Char ** start is used for a large amount of data and needs to be read multiple times. When offset is not 0, it indicates that it is not the first read. We should point out the starting position of data storage, * start. Generally, the system will use the same Buf for reading. In a non-first read, we can choose to store the BUF + offset until the Buf is full, we can also set the starting position for storing data freely. For example, in the above example, you can modify it to return only one message each time, instead of trying to read it multiple times. In short, we should set * start to specify the initial position when not reading the data for the first time. For the first read, the default value starts from the first byte of the Buf. You may also set the * start value.

  

Below are some of the input results of dmesg of total_index = 100:

============= Buf effb1000 * Start 00000000 offset 0 Len 0 EOF 0 count 1024 limit 944

=== Buf effb1000 * Start effb1000 offset 0 Len 0 EOF 0 count 1024 limit 944

000 Len = 0 12345678901234567890123456789901234567890

001 Len = 52 12345678901234567890123456789901234567890

002 Len = 105 12345678901234567890123456789901234567890

003 len = 159 12345678901234567890123456789901234567890

004 len = 213 12345678901234567890123456789901234567890

005 len = 267 12345678901234567890123456789901234567890

006 len = 321 12345678901234567890123456789901234567890

007 len = 375 12345678901234567890123456789901234567890

008 len = 429 12345678901234567890123456789901234567890

......

017 len = 915 12345678901234567890123456789901234567890

=== Return len = 969 limit = 944 eof = 1 index = 18

============= Buf effb1000 * start 00000000 offset 969 len 0 eof 0 count 1024 limit 944

=== Buf effb1000 * start effb1000 offset 969 len 0 eof 0 count 1024 limit 944

018 len = 0 12345678901234567890123456789901234567890

.......

035 len = 915 12345678901234567890123456789901234567890

=== Return len = 969 limit = 944 eof = 1 index = 36

============= Buf effb1000 * start 00000000 offset 1938 len 0 eof 0 count 1024 limit 944

=== Buf effb1000 * start effb1000 offset 1938 len 0 eof 0 count 1024 limit 944

036 len = 0 12345678901234567890123456789901234567890

.......

089 len = 915 12345678901234567890123456789901234567890

=== Return len = 969 limit = 944 eof = 1 index = 90

============= Buf effb1000 * start 00000000 offset 4845 len 0 eof 0 count 1024 limit 944

=== Buf effb1000 * start effb1000 offset 4845 len 0 eof 0 count 1024 limit 944

090 len = 0 12345678901234567890123456789901234567890

......

099 len = 483 12345678901234567890123456789901234567890

=== Return len = 537 limit = 944 eof = 1 index = 100

============= Buf effb1000 * Start 00000000 offset 5382 Len 0 EOF 0 count 1024 limit 944

= _ = Return Len = 0 Limit = 944 EOF = 0

The following is an example of scull memory information. If scullx's scull_qset contains data, we will display the location of each quantum in the qset queue, including the unallocated quantum (show 0, this generally requires multiple reads ). For multiple reads, the biggest problem is that we need to remember how much information has been read last time, that is, we should read from that one time. In the last chapter of ldd3, Goto statements are common in kernel programs. This is different from the case where we scold goto as a zombie when learning programming, this is related to the event trigger mechanism of the kernel. In this example, when the buffer read is full, we use goto to provide a uniform exit, so that the entire program looks neat and easy to read.

Static int store_dev_num = 0, store_qs_num = 0, store_quan_num = 0;

Int scull_read_procmem (char * buf, char ** start, off_t offset, int count, int * eof, void * data)

{

Int I, j, Len = 0, K = 0;

Int Limit = count-80;

/* If all the data has been read, store_dev_num will be equal to scull_dev_num. We use the following three parameters to record the device being read, The qset to be read, and the quantum in the qset */

If (store_dev_num> = scull_dev_num ){

Store_dev_num = 0;

Store_qs_num = 0;

Store_quan_num = 0;

Return 0;

}

/* Used for multiple reads */

If (offset> 0 ){

* Start = buf;

}

For (I = store_dev_num; I <SCULL_DEV_NUM & len <= limit; I ++ ){

Struct scull_dev * dev = & mydev [I];

Struct scull_qset * Qs = Dev-> data;

If (len> limit)

Goto buffer_full;

If (down_interruptible (& Dev-> SEM ))

Return-erestartsys;

If (! Store_qs_num &&! Store_quan_num ){

Len + = sprintf (BUF + Len, "/n device scull % d: qset % I, Q % I SZ % Li/N ",

I, dev-> qset, dev-> quantum, dev-> size );

}

K = 0;

For (; qs = qs-> next, k ++ ){

If (k <store_qs_num)

Continue;
If (LEN> limit ){

Up (& Dev-> SEM );

Goto buffer_full;

}

If (! Store_quan_num ){

Len + = sprintf (buf + len, "item at % p, qset % d at % p/n", qs, k ++, qs-> data );

}

If (qs-> data ){

J = store_quan_num;

/* The comments below are used to display all the quantum positions in the queue. it is ensured that the output information is sufficient,

* At last, the program is restored and only the valid part is displayed. */

For (; j <Dev-> qset/* & QS-> data [J] */

; J ++ ){

If (LEN> limit ){

Up (& dev-> sem );

Goto buffer_full;

}

Len + = sprintf (buf + len, "/t % 4i: % 8 p/n", j, qs-> data [j]);

Store_quan_num ++;

}

Store_quan_num = 0;

}

Store_qs_num ++;

}

Up (& dev-> sem );

Store_dev_num ++;

Store_qs_num = 0;

}

Buffer_full:

* Eof = 1;

Return len;

}

From the code above, there is a potential danger, that is, if the scull device is being written during information reading. After a read/write operation is complete, we release the semaphore and wait for the next read/write operation. In this process, the semaphore may be occupied by the write operation. In this example, we only read the location information. Even if there is a problem, it will not have much impact,In actual application, we should perform operations on a device in a semaphore hold.
For example, consider caching unread information, waiting for the next read, or minimizing unnecessary information.[Programming philosophy 2: semaphore holding and operations]

Related technical articles:

My articles related to the kernel module

My articles related to programming ideas

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.