Lesson 11: Adding "proc" files to your modules -- Part 1

So what's happening here?

Given the amount of material to be covered in discussing Linux "proc" files, this lesson is going to discuss the creation and usage of an extremely simple example of a proc file, while the next lesson (possibly two) will return to dig into the details and cover the underlying intricacies.

So have no fear if we gloss over a few (or many) details on this page -- it will all be eventually covered in time.

What's the /proc directory about, anyway?

Most Linux users are familiar (at least in passing) with the contents of the /proc directory and some of its useful files. As an example, if you were curious about what version of the kernel you were running, you could simply:

Exercise for the student: Poke around under /proc and identify any files that seem to contain useful information you could imagine needing at some point.

/proc as a "pseudo" filesystem

At this point, the most important observation to make about the /proc directory is that, technically, it doesn't exist. By that, I mean that it is not a normal directory containing normal text files that are sitting there, taking up space on the hard disk.

Rather, the /proc directory represents what is the called the proc "pseudo" filesystem, in that all of the files you see there are simply access points (or windows, or whatever you want to call them) onto code running in kernel space, and any user space access of those files translates into a call to kernel space, to the kernel code associated with that proc file, which dynamically generates the output to be handed back to user space that, from your perspective, looks like simple file contents.

Put more succinctly, any access to a proc file maps to some underlying kernel code that's responsible for handling any read or write request for that file, in whatever way it wants. It just looks like a normal file to you.

Actually, it's not hard to see that there's something odd about that directory, Consider the long listing of that version file we listed earlier:

That's a bit unusual, wouldn't you say -- a file of size zero that still appears to contain data. The other observation is that the entire /proc directory clearly represents a mount point of type "/proc":

$ mount
...
proc on /proc type proc (rw,noexec,nosuid,nodev)
...
$

which is also a dead giveaway that this is no normal directory. And there's one final (admittedly trivial) observation to make.

Some proc files are expected to return the same string each time, since they represent information that should be constant, such as /proc/version. Other proc files, such as /proc/meminfo, need to generate their "contents" from scratch every time you access them since you expect them to be changing constantly.

As I said, not a deep observation, just something that will affect how complicated the code is for a given proc file. And now, let's look at an actual example.

Let's talk about /proc/version

Many of the simpler proc files are implemented by a single source file under the fs/proc directory in the kernel source tree -- in this case, we can see what code implements the /proc/version file:

In other words, from within kernel space, the routine seq_printf() is invoked with the destination argument of a given seq_file that just happens to correspond to what you see in user space as the /proc/version file. And the eventual contents are defined by whatever the original kernel programmer decided would be gathered from various places around kernel space and bundled into a single string, printf-style.

Exercise for the student: Take a look at the kernel source file fs/proc/meminfo.c to see how much running around that code must do to generate the contents of the /proc/meminfo file when you list that file.

Is that version stuff module code or not?

Sharp-eyed readers will have noticed the module_init() macro at the bottom of the version code and assumed that this is meant to be loaded as a module. Actually, no. Even code that is built into the kernel can have entry/initialization code. The fact that that's not module code is obvious from the fact that there's no exit routine (remember the trouble you ran into when you tried to load a module with no exit routine?).

But there's another way to tell that that code above is designed to be explicitly built into the kernel and never buildable as a module.

Examine, if you will, the kernel makefile that controls the building of various proc files:

If you've never seen makefiles like the above before, no problem -- they're easy to explain.

Note that some of the source files in the fs/proc directory are not even configurable and will always be compiled into the kernel (those are the "proc-y" files), while others will be compiled in based on your configuration. The only point we're making here is that, depending on how your kernel was configured and built, you may not have every possible proc file on your system.

Related to our example, it's obvious that you should always have a /proc/version file, which is why the corresponding source file has no exit routine -- that code can't ever be possibly unloaded so we don't need one.

Creating your own trivial proc file

At this point, given that you have a model source file to steal from (fs/proc/version.c), it should be a simple matter to write a loadable module that creates a proc file that allows you to print whatever you want from kernel space. In our case, let's write a module that lets us display the current value of jiffies:

Without getting into major detail (we'll do that next lesson), you should be able to compile the above, and load it, at which point a read-only file named /proc/crash_jiffies should magically appear which allows you to list the jiffies value any time you want until you unload that module.

Exercise for the student: What's the most obvious difference between your module code and the code in version.c? Explain that difference.

What that statement means is that, every time you attempt to list the contents of a /proc file, it's up to the code in kernel space to generate, on the fly, the output to be passed back to user space. It means that the output can change from call to call since the kernel code is responsible for regenerating that output each time. It might pass back the same output each time, or it might not.

1) in my code I included module.h was not present in version.c
2) we also inlcuded jiffies.h while in version.c utsname.c
was included
3) utsname seems to be some system call which was used in version.c
4) function proc_create different arguments passed
5) there was no exit code for version.c where as in our was there