Solve Non-linear Equations using Linear Algebra

This eighth article of the mathematical journey through open source, solves non-linear equations using linear algebra in octave.

<< Seventh Article

Hope you have found out the vegetable prices from the vegetable seller, who had placed various equal priced stacks for sell at ₹30. Recall: One stack had 4 lemons, 7 cucumbers, 9 tomatoes. Another had 2 lemons, 5 cucumbers, 27 tomatoes. And the third had just 9 cucumbers & 15 tomatoes. Prices would be ₹2.00 per lemon, ₹2.50 per cucumber, ₹0.50 per tomato, computed as follows:

$ octave -qf
octave:1> N = [
> 4 7 9
> 2 5 27
> 0 9 15
> ];
octave:2> inv(N) * [30; 30; 30]
ans =

   2.00000
   2.50000
   0.50000

octave:3>

Polynomial solving

Note that though in 3 variables, even this was a linear equation. How about solving higher order polynomial equations, meaning of squares, cubes, … of the variables. Say, we want a solution for x in x3 + 3x2 + 3x + 1 = 0. Simple! First define a function for this polynomial. And, then use the function solver fsolve() to solve it, as follows:

$ octave -qf
octave:1> function y = f(x)
> y = x^3 + 3*x^2 + 3*x + 1;
> endfunction
octave:2> [x, fval, info] = fsolve(@f, 0)
x = -0.99999
fval = 0
info =  1
octave:3>

This indicates the value of x as -0.99999 ≈ -1 as the solution to the function f(x), yielding a function value of 0, with info = 1 indicating that solution is obtained. And you may verify the answer by calling the function f with the variable x as f(x) on the octave prompt. The second parameter in fsolve() is the initial guess of the solution.

Geometry solving

With the power in hand, why not solve more complex geometric problems? Last time we found the intersection point of two straight lines. How about intersection of a straight line and a circle? Let us have the following straight line and circle, defined in the Cartesian coordinate system, i.e. the x-y system:
4x + 3y = 24
x2 + y2 = 25

To be able to solve it using fsolve(), let’s consider the different variables x & y as fields of a vector X, say x as X(1), y as X(2). Then, the equations can be re-written as follows:
4 * X(1) + 3 * X(2) = 24
X(1)^2 + X(2)^2 = 25
and hence could be solved using fsolve() as follows:

$ octave -qf
octave:1> function Y = F(X)
> Y(1) = 4 * X(1) + 3 * X(2) - 24;
> Y(2) = X(1)^2 + X(2)^2 - 25;
> endfunction
octave:2> [Y, Fval, info] = fsolve(@F, [0; 0])
warning: matrix singular to machine precision, rcond = 0
warning: attempting to find minimum norm solution
warning: dgelsd: rank deficient 2x2 matrix, rank = 1
Y =

   3.0000
   4.0000

Fval =

   0.0000e+00   2.6691e-07

info =  1
octave:3>

So, (3, 4) is the intersecting point – can be verified by substituting back into the above equations.

Solve it

Equipped with this knowledge, here’s a couple of teasers for your brain:

  1. Find three numbers, product of which is 60; sum of their squares is 50; and their sum is 12.
  2. A sage came to a temple with some flowers and dipped all of them into the first magical pond of the temple and got those back, squared. Then, he offered some of those flowers in the temple and dipped the remaining flowers into the second magical pond to get those back, doubled. Then, he again offered the same number of flowers, as offered earlier, and dipped the remaining flowers into the third magical pond to get those back, tripled, which he took back with him as prasadam. Now, the number of flowers he took back with him, is same as in each one of his offerings. Also, what he took back with him is thrice the number of flowers he came with to the temple. How many flowers did he come in with?

If you think, you have got the octave code for solving the above, you may post the solution in the comments below. And as we move on, we would get into specifically playing with polynomials.

Ninth Article >>

   Send article as PDF   

Generic Hardware Access in Linux

This seventh article, which is part of the series on Linux device drivers, talks about accessing hardware in Linux.

<< Sixth Article

Shweta was all jubilant about her character driver achievements, as she entered the Linux device drivers laboratory on the second floor of her college. Why not? Many of her classmates had already read her blog & commented on her expertise. And today was a chance for show-off at an another level. Till now, it was all software. Today’s lab was on accessing hardware in Linux. Students are expected to “learn by experimentation” to access various kinds of hardware in Linux on various architectures over multiple lab sessions here.

As usual, the lab staff are a bit skeptical to let the students directly get onto the hardware, without any background. So to build their background, they have prepared some slide presentations, which can be accessed from SysPlay’s website.

Generic hardware interfacing

As every one settled in the laboratory, lab expert Priti started with the introduction to hardware interfacing in Linux. Skipping the theoretical details, the first interesting slide was about the generic architecture-transparent hardware interfacing. See Figure 11.

Figure 11: Hardware mapping

Figure 11: Hardware mapping

The basic assumption being that the architecture is 32-bit. For others, the memory map would change accordingly. For 32-bit address bus, the address/memory map ranges from 0 (0x00000000) to ‘232 – 1′ (0xFFFFFFFF). And an architecture independent layout of this memory map would be as shown in the Figure 11 – memory (RAM) and device regions (registers & memories of devices) mapped in an interleaved fashion. The architecture dependent thing would be what these addresses are actually there. For example, in an x86 architecture, the initial 3GB (0x00000000 to 0xBFFFFFFF) is typically for RAM and the later 1GB (0xC0000000 to 0xFFFFFFFF) for device maps. However, if the RAM is less, say 2GB, device maps could start from 2GB (0x80000000).

Type in cat /proc/iomem to list the memory map on your system. cat /proc/meminfo would give you an approximate RAM size on your system. Refer to Figure 12 for a snapshot.

Figure 12: Physical & bus addresses on an x86 system

Figure 12: Physical & bus addresses on an x86 system

Irrespective of the actual values, the addresses referring to RAM are termed as physical addresses. And the addresses referring to device maps are termed as bus addresses, as these devices are always mapped through some architecture-specific bus. For example, PCI bus in x86 architecture, AMBA bus in ARM architectures, SuperHyway bus in SuperH (or SH) architectures, GX bus on PowerPC (or PPC), etc.

All the architecture dependent values of these physical and bus addresses are either dynamically configurable or are to be obtained from the datasheets (i.e. hardware manuals) of the corresponding architecture processors/controllers. But the interesting part is that, in Linux none of these are directly accessible but are to be mapped to virtual addresses and then accessed through that. Thus, making the RAM and device accesses generic enough, except just mapping them to virtual addresses. And the corresponding APIs for mapping & unmapping the device bus addresses to virtual addresses are:

#include <asm/io.h>

void *ioremap(unsigned long device_bus_address, unsigned long device_region_size);
void iounmap(void *virt_addr);

These are prototyped in <asm/io.h>. Once mapped to virtual addresses, it boils down to the device datasheet, as to which set of device registers and/or device memory to read from or write into, by adding their offsets to the virtual address returned by ioremap(). For that, the following are the APIs (prototyped in the same header file <asm/io.h>):

#include <asm/io.h>

unsigned int ioread8(void *virt_addr);
unsigned int ioread16(void *virt_addr);
unsigned int ioread32(void *virt_addr);
unsigned int iowrite8(u8 value, void *virt_addr);
unsigned int iowrite16(u16 value, void *virt_addr);
unsigned int iowrite32(u32 value, void *virt_addr);

Accessing the video RAM of “DOS” days

After this first set of information, students were directed for the live experiments. They were suggested to do an initial experiment with the video RAM of “DOS” days to understand the usage of the above APIs. Shweta got onto the system – displayed the /proc/iomem window – one very similar to as shown in Figure 12. From there, she got the video RAM address ranging from 0x000A0000 to 0x000BFFFF. And with that she added the above APIs with appropriate parameters into the constructor and destructor of her already written null driver to convert it into a vram driver. Then, she added the user access to the video RAM through read & write calls of the vram driver. Here’s what she coded in the new file video_ram.c:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/io.h>

#define VRAM_BASE 0x000A0000
#define VRAM_SIZE 0x00020000

static void __iomem *vram;
static dev_t first;
static struct cdev c_dev;
static struct class *cl;

static int my_open(struct inode *i, struct file *f)
{
	return 0;
}
static int my_close(struct inode *i, struct file *f)
{
	return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	int i;
	u8 byte;

	if (*off >= VRAM_SIZE)
	{
		return 0;
	}
	if (*off + len > VRAM_SIZE)
	{
		len = VRAM_SIZE - *off;
	}
	for (i = 0; i < len; i++)
	{
		byte = ioread8((u8 *)vram + *off + i);
		if (copy_to_user(buf + i, &byte, 1))
		{
			return -EFAULT;
		}
	}
	*off += len;

	return len;
}
static ssize_t my_write(
		struct file *f, const char __user *buf, size_t len, loff_t *off)
{
	int i;
	u8 byte;

	if (*off >= VRAM_SIZE)
	{
		return 0;
	}
	if (*off + len > VRAM_SIZE)
	{
		len = VRAM_SIZE - *off;
	}
	for (i = 0; i < len; i++)
	{
		if (copy_from_user(&byte, buf + i, 1))
		{
			return -EFAULT;
		}
		iowrite8(byte, (u8 *)vram + *off + i);
	}
	*off += len;

	return len;
}

static struct file_operations vram_fops =
{
	.owner = THIS_MODULE,
	.open = my_open,
	.release = my_close,
	.read = my_read,
	.write = my_write
};

static int __init vram_init(void) /* Constructor */
{
	int ret;
	struct device *dev_ret;

	if ((vram = ioremap(VRAM_BASE, VRAM_SIZE)) == NULL)
	{
		printk(KERN_ERR "Mapping video RAM failed\n");
		return -ENOMEM;
	}
	if ((ret = alloc_chrdev_region(&first, 0, 1, "vram")) < 0)
	{
		return ret;
	}
	if (IS_ERR(cl = class_create(THIS_MODULE, "chardrv")))
	{
		unregister_chrdev_region(first, 1);
		return PTR_ERR(cl);
	}
	if (IS_ERR(dev_ret = device_create(cl, NULL, first, NULL, "vram")))
	{
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return PTR_ERR(dev_ret);
	}

	cdev_init(&c_dev, &vram_fops);
	if ((ret = cdev_add(&c_dev, first, 1)) < 0)
	{
		device_destroy(cl, first);
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return ret;
	}
	return 0;
}

static void __exit vram_exit(void) /* Destructor */
{
	cdev_del(&c_dev);
	device_destroy(cl, first);
	class_destroy(cl);
	unregister_chrdev_region(first, 1);
	iounmap(vram);
}

module_init(vram_init);
module_exit(vram_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("Video RAM Driver");

Summing up

Then, Shweta repeated the following steps:

  • Build the vram driver (video_ram.ko file) by running make with the same Makefile changed to build this driver.
  • Usual load of the driver using insmod video_ram.ko.
  • Usual write into /dev/vram, say using echo -n “0123456789” > /dev/vram.
  • Read the /dev/vram contents using xxd /dev/vram | less. The usual cat /dev/vram also can be used but that would give all binary content. xxd shows them up as hexadecimal in centre with the corresponding ASCII along the right side.
  • Usual unload the driver using rmmod video_ram.

Note 1: Today’s systems typically use separate video cards having their own video RAM. So, the video RAM used in the “DOS days”, i.e. the one mentioned in this article is unused and many a times not even present. Hence, playing around with it, is safe, without any effect on the system, or the display.

Note 2: Moreover, if the video RAM is absent, the read/write may not be actually reading/writing, but just sending/receiving signals in the air. In such a case, writes would not do any change, and reads would keep on reading the same value – thus ‘xxd’ showing the same values.

It was yet half an hour left for the practical class to be over and a lunch break. So, Shweta decided to walk around and possibly help somebody in their experiments.

Eighth Article >>

Notes:

  1. When a pointer is tagged with __iomem, it enables that pointer for compiler checks &/or optimizations, relevant for I/O mapped memory.

Other References:

  1. Translating addresses in Kernel Space on different architectures
  2. Addressing Concepts in Linux
  3. Linux Memory Management Overview
   Send article as PDF   

Solve Puzzles using Linear Algebra

This seventh article of the mathematical journey through open source, solves puzzles using linear algebra in octave.

<< Sixth Article

Matrix Maths is what is formally called Linear Algebra. We have gone through its basics in the fifth article. Now, we shall apply that to practical usage. What better than solving puzzles using the same.

Purchase Solving

Shrishti purchased 24 pencils and 12 erasers for ₹96. Divya purchased 20 pencils and 15 erasers for ₹100. What are the prices of the pencil & the eraser?

Assuming that ‘p’ is the price for pencils & ‘e’ is the price for erasers, we have the following two equations:
24 * p + 12 * e = 96
20 * p + 15 * e = 100
Hence, we could get the values of ‘p’ & ‘e’ by solving these equations. Converting them into linear algebra form, they can be re-written using matrix multiplication as:
┏          ┓┏    ┓    ┏        ┓
┃24 12 ┃┃ p ┃    ┃   96 ┃
┃20 15 ┃┃ e ┃ = ┃ 100 ┃
┗          ┛┗    ┛    ┗        ┛
which is Ax = b, ‘x’ being the vector with variables ‘p’ & ‘e’. Hence, we need to solve for x, which is given by: x = A-1b. Using octave:

$ octave -qf
octave:1> A = [
> 24 12
> 20 15
> ];
octave:2> b = [
> 96
> 100
> ];
octave:3> x = inv(A) * b
x =

   2.0000
   4.0000

octave:4>

Hence, p = ₹2 and e = ₹4, i.e. each pencil costs ₹2 and an eraser costs ₹4. You may check by putting back these values in our problem statement. Isn’t that cool?

Geometry Solving

How about finding the intersection point of two straight lines? Let us have the following 2 straight lines, defined in the Cartesian coordinate system, i.e. the x-y system:
4x + 3y = 24
3x + 4y = 25

Similar to the earlier problem, the intersecting point could be obtained as follows:

$ octave -qf
octave:1> A = [
> 4 3
> 3 4
> ];
octave:2> b = [
> 24
> 25
> ];
octave:3> X = inv(A) * b
X =

   3.0000
   4.0000

octave:4>

So, (3, 4) is the intersecting point. Want to see it visually. For that, we would just need to rewrite the straight line equations as follows:
y = (24 – 4x) / 3
y = (25 – 3x) / 4
And then here goes the code:

octave:1> x=-10:0.01:10;
octave:2> plot(x, (24 - 4*x)/3, "b.", x, (25 - 3*x)/4, "g.");
octave:3>

Figure 5 shows the plot generated by the above code.

Figure 5: Intersection of straight lines

Figure 5: Intersection of straight lines

Solve it

Equipped with the puzzle solving basics, here’s one for your brain: A vegetable seller has placed various equal priced stacks for sale at ₹30. One stack has 4 lemons, 7 cucumbers, 9 tomatoes. Another has 2 lemons, 5 cucumbers, 27 tomatoes. And the third has just 9 cucumbers & 15 tomatoes. Can you compute the price of each vegetable?

Hint: Assume the price of lemon, cucumber, tomato as ‘l’, ‘c’, ‘t’, and then form the 3 equations in three variables.

If you think, you have got it, you may post the solution in the comments. And as we move on, we will get into some different kind of puzzle solving.

Eighth Article >>

   Send article as PDF   

Decoding the character device file operations

This sixth article, which is part of the series on Linux device drivers, is continuation of the various concepts of character drivers and their implementation, dealt with in the previous two articles.

<< Fifth Article

So, what was your guess on how would Shweta crack the nut? Obviously, using the nut cracker named Pugs. Wasn’t it obvious? <Smile> In our previous article, we saw how Shweta was puzzled with reading no data, even after writing into the /dev/mynull character device file. Suddenly, a bell rang – not inside her head, a real one at the door. And for sure, there was the avatar of Pugs.

“How come you’re here?”, exclaimed Shweta. “After reading your tweet, what else? Cool that you cracked your first character driver all on your own. That’s amazing. So, what are you up to now?”, said Pugs. “I’ll tell you on the condition that you do not become a spoil sport”, replied Shweta. “Okay yaar, I’ll only give you pointers”. “And that also, only if I ask for”. “Okie”. “I am trying to decode the working of character device file operations”. “I have an idea. Why don’t you decode and explain me your understanding?”. “Not a bad idea”. With that, Shweta tailed the dmesg log to observe the printk‘s output from her driver. Alongside, she opened her null driver code on her console, specifically observing the device file operations my_open, my_close, my_read, and my_write.

static int my_open(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: open()\n");
	return 0;
}
static int my_close(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: close()\n");
	return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	return 0;
}
static ssize_t my_write(
		struct file *f, const char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: write()\n");
	return len;
}

Based on the earlier understanding of return value of the functions in kernel, my_open() and my_close() are trivial. Their return types being int and both of them returning zero, meaning success. However, the return types of both my_read() and my_write() are not int, but ssize_t. On further digging through kernel headers, that turns out to be signed word. So, returning a negative number would be a usual error. But a non-negative return value would have an additional meaning. For read it would be number of bytes read, and for write it would be number of bytes written.

Reading the device file

For understanding this in detail, the complete flow has to be re-looked at. Let’s take read first. So, when the user does a read onto the device file /dev/mynull, that system call comes to the virtual file system (VFS) layer in the kernel. VFS decodes the <major, minor> tuple & figures out that it need to redirect it to the driver’s function my_read(), registered with it. So from that angle, my_read() is invoked as a request to read, from us – the device driver writers. And hence, its return value would indicate to the requester – the user, as to how many bytes is he getting from the read request. In our null driver example, we returned zero – meaning no bytes available or in other words end of file. And hence, when the device file is being read, the result is always nothing, independent of what is written into it.

“Hmmm!!! So, if I change it to 1, would it start giving me some data?”, Pugs asked in his verifying style. Shweta paused for a while – looked at the parameters of the function my_read() and confirmed with a but – data would be sent but it would be some junk data, as the my_read() function is not really populating the data into the buf (second parameter of my_read()), provided by the user. In fact, my_read() should write data into buf, according to len (third parameter of my_read()), the count in bytes requested by the user.

To be more specific, write less than or equal to len bytes of data into buf, and the same number be used as the return value. It is not a typo – in read, we ‘write’ into buf – that’s correct. We read the data from (possibly) an underlying device and then write that data into the user buffer, so that the user gets it, i.e. reads it. “That’s really smart of you”, expressed Pugs with sarcasm.

Writing into the device file

Similarly, the write is just the reverse procedure. User provides len (third parameter of my_write()) bytes of data to be written, into buf (second parameter of my_write()). my_write() would read that data and possibly write into an underlying device, and accordingly return the number of bytes, it has been able to write successfully. “Aha!! That’s why all my writes into /dev/mynull have been successful, without being actually doing any read or write”, exclaimed Shweta filled with happiness of understanding the complete flow of device file operations.

Preserving the last character

That was enough – Shweta not giving any chance to Pugs to add, correct or even speak. So, Pugs came up with a challenge. “Okay. Seems like you are thoroughly clear with the read/write funda. Then, here’s a question for you. Can you modify these my_read() and my_write() functions such that whenever I read /dev/mynull, I get the last character written into /dev/mynull?”

Confident enough, Shweta took the challenge and modified the my_read() and my_write() functions as follows, along with an addition of a static global character:

static char c;

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	buf[0] = c;
	return 1;
}
static ssize_t my_write(
		struct file *f, const char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: write()\n");
	c = buf[len – 1];
	return len;
}

“Almost there, but what if the user has provided an invalid buffer, or what if the user buffer is swapped out. Wouldn’t this direct access of user space buf just crash and oops the kernel”, pounced Pugs. Shweta not giving up the challenge, dives into her collated material and figures out that there are two APIs just to ensure that the user space buffers are safe to access and then update them, as well. With the complete understanding of the APIs, she re-wrote the above code snippet along with including the corresponding header <asm/uaccess.h>, as follows, leaving no chance for Pugs to comment:

#include <asm/uaccess.h>

static char c;

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	if (copy_to_user(buf, &c, 1) != 0)
		return -EFAULT;
	else
		return 1;
}
static ssize_t my_write(
		struct file *f, const char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: write()\n");
	if (copy_from_user(&c, buf + len – 1, 1) != 0)
		return -EFAULT;
	else
		return len;
}

Then, Shweta repeated the usual build and test steps as follows:

  • Build the modified null driver (.ko file) by running make.
  • Load the driver using insmod.
  • Write into /dev/mynull, say using echo -n “Pugs” > /dev/mynull
  • Read from /dev/mynull using cat /dev/mynull (Stop using Ctrl+C)
  • Unload the driver using rmmod.

Summing up

On cat‘ing /dev/mynull, the output was a non-stop infinite sequence of ‘s’, as my_read() gives the last one character forever. So, Pugs intervenes and presses Ctrl+C to stop the infinite read, and tries to explain, “If this is to be changed to ‘the last character only once’, my_read() needs to return 1 the first time and zero from second time onwards. This can be achieved using the off (fourth parameter of my_read())”. Shweta nods her head to support Pugs’ ego.

Seventh Article >>

Add-on

And here’s the modified read using the off:

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	if (*off == 0)
	{
		if (copy_to_user(buf, &c, 1) != 0)
			return -EFAULT;
		else
		{
			(*off)++;
			return 1;
		}
	}
	else
		return 0;
}
   Send article as PDF   

The Imaginary Music of Octave

This sixth article of the mathematical journey through open source, introduces you to the imaginary numbers through octave.

<< Fifth Article

Yes, we have done the powerful matrix math with octave, without even thinking of how it is internally done. Now, on the same note, let’s continue to explore one of the most fascinating or rather most imaginary stuff of math.

i Fun

Yes what else the imaginary numbers, numbers which really don’t exist but we try to visualize them as points on a 2-D plane with the real part on x-axis and the imaginary part on y-axis. So to plot the imaginary i, with a blue star (*), issue the following command at the octave prompt:

$ octave -qf
octave:1> plot(i, "b*")
octave:2>

and here pops the display window as shown in Figure 3.

Figure 3: Plot of i (sqrt(-1))

Figure 3: Plot of i (sqrt(-1))

What is this i? Just square root of -1. Simple! Right? Remember, we got an error trying sqrt(-1) in bc. octave is neat. It will answer you politely with an i. Still confused. Just check out the following:

$ octave -qf
octave:1> i
ans =  0 + 1i
octave:2> i * i
ans = -1
octave:3> sqrt(-1)
ans =  0 + 1i
octave:4> i^3
ans = -0 - 1i
octave:5>

i Puzzle

And now comes the puzzle part. What is the imaginary part of ii? It’s zero. What? Is it a real number? Yes it is. It is in fact equal to e-π/2. Check out for yourself:

$ octave -qf
octave:1> i^i
ans =  0.20788
octave:2> exp(-pi/2)
ans =  0.20788
octave:3> 

Yes, you don’t really need to bother about how. octave just gets you there. And that’s fun with mathematics without getting into mathematics.

i: just a number

If you are not puzzled by this, great! In fact, as the imaginary numbers are numbers, just special numbers, in octave, they can be used in any operations you may think of with numbers. Try out sqrt, exp, log – addition, subtraction, multiplication, division are just trivial ones. And more fun would be with the octave power of vectors & matrices. Watch out:

$ octave -qf
octave:1> sqrt(i)
ans =  0.70711 + 0.70711i
octave:2> exp(i)
ans =  0.54030 + 0.84147i
octave:3> log(i)
ans =  0.00000 + 1.57080i
octave:4>  [i  # Press <Enter> here
>           i] * [i i]
ans =

  -1  -1
  -1  -1

octave:5>

Is e really cos θ + i sin θ?

A very simple check. Let’s plot both the curves. Let θ (theta) be set to values from 0 to π (pi) with say intervals of 0.01, and then let’s plot the two curves in blue and red:

$ octave -qf
octave:1> th=0:0.01:pi;
octave:2> plot(th, exp(i*th), "b*;exp;", th, cos(th)+i*sin(th), "r^;cs;")
octave:3> 

Figure 4 shows the plot with the 2 curves coinciding exactly with each other.

Figure 4: Plot of Euler's equality

Figure 4: Plot of Euler’s equality

Equipped with i, let’s play out with more puzzles, as we move on. Get ready with octave controls.

Seventh Article >>

   Send article as PDF   

Character device files: Creation & Operations

This fifth article, which is part of the series on Linux device drivers, is continuation of the various concepts of character drivers and their implementation, dealt with in the previous article.

<< Fourth Article

In our previous article, we noted that even with the registration for <major, minor> device range, the device files were not created under the /dev, rather Shweta had to create them by hand using mknod. However, on further study, Shweta figured out a way for the automatic creation of the device files using the udev daemon. She also learnt the second step for connecting the device file with the device driver – “Linking the device file operations to the device driver functions”. Here are her learnings.

Automatic creation of device files

Earlier in kernel 2.4, automatic creation of device files was done by the kernel itself, by calling the appropriate APIs of devfs. However, as kernel evolved, kernel developers realized that device files are more of a user space thing and hence as a policy only the users should deal with it, not the kernel. With this idea, now kernel only populates the appropriate device class & device info into the /sys window for the device under consideration. And then, the user space need to interpret it and take an appropriate action. In most Linux desktop systems, the udev daemon picks up that information and accordingly creates the device files.

udev can be further configured using its configuration files to tune the device file names, their permissions, their types, etc. So, as far as driver is concerned, the appropriate /sys entries need to be populated using the Linux device model APIs declared in <linux/device.h> and the rest would be handled by udev. Device class is created as follows:

struct class *cl = class_create(THIS_MODULE, "<device class name>");

and then the device info (<major, minor>) under this class is populated by:

device_create(cl, NULL, first, NULL, "<device name format>", …);

where first is the dev_t with the corresponding <major, minor>.

The corresponding complementary or the inverse calls, which should be called in chronologically reverse order, are as follows:

device_destroy(cl, first);
class_destroy(cl);

Refer to Figure 9, for the /sys entries created using “chardrv” as the <device class name> and “mynull” as the <device name format>. That also shows the device file, created by udev, based on the <major>:<minor> entry in the dev file.

Figure 9: Automatic device file creation

Figure 9: Automatic device file creation

In case of multiple minors, device_create() and device_destroy() APIs may be put in for-loop, and the <device name format> string could be useful. For example, the device_create() call in a for-loop indexed by ‘i‘ could be as follows:

device_create(cl, NULL, MKDEV(MAJOR(first), MINOR(first) + i), NULL, "mynull%d", i);

File operations

Whatever system calls or more commonly file operations we talk of over a regular file, are applicable to the device files as well. That’s what we say a file is a file, and in Linux almost everything is a file from user space perspective. The difference lies in the kernel space, where virtual file system (VFS) decodes the file type and transfers the file operations to the appropriate channel, like file system module in case of a regular file or directory, corresponding device driver in case of a device file. Our discussion of interest is the second case.

Now, for VFS to pass the device file operations onto the driver, it should have been told about that. And yes, that is what is called registering the file operations by the driver with the VFS. This involves two steps. (The parenthesised text below refers to the ‘null driver’ code following it.) First, is to fill in a file operations structure (struct file_operations pugs_fops) with the desired file operations (my_open, my_close, my_read, my_write, …) and to initialize the character device structure (struct cdev c_dev) with that, using cdev_init(). The second step is to hand this structure to the VFS using the call cdev_add(). Both cdev_init() and cdev_add() are declared in <linux/cdev.h>. Obviously, the actual file operations (my_open, my_close, my_read, my_write) also had to be coded by Shweta. So, to start with, Shweta kept them as simple as possible, so as to say, as easy as the “null driver”.

The null driver

Following these steps, Shweta put all the pieces together to attempt her first character device driver. Let’s see what was the outcome. Here’s the complete code:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class

static int my_open(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: open()\n");
	return 0;
}
static int my_close(struct inode *i, struct file *f)
{
	printk(KERN_INFO "Driver: close()\n");
	return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
	printk(KERN_INFO "Driver: read()\n");
	return 0;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len,
	loff_t *off)
{
	printk(KERN_INFO "Driver: write()\n");
	return len;
}

static struct file_operations pugs_fops =
{
	.owner = THIS_MODULE,
	.open = my_open,
	.release = my_close,
	.read = my_read,
	.write = my_write
};

static int __init ofcd_init(void) /* Constructor */
{
	int ret;
	struct device *dev_ret;

	printk(KERN_INFO "Namaskar: ofcd registered");
	if ((ret = alloc_chrdev_region(&first, 0, 1, "Shweta")) < 0)
	{
		return ret;
	}
	if (IS_ERR(cl = class_create(THIS_MODULE, "chardrv")))
	{
		unregister_chrdev_region(first, 1);
		return PTR_ERR(cl);
	}
	if (IS_ERR(dev_ret = device_create(cl, NULL, first, NULL, "mynull")))
	{
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return PTR_ERR(dev_ret);
	}

	cdev_init(&c_dev, &pugs_fops);
	if ((ret = cdev_add(&c_dev, first, 1)) < 0)
	{
		device_destroy(cl, first);
		class_destroy(cl);
		unregister_chrdev_region(first, 1);
		return ret;
	}
	return 0;
}

static void __exit ofcd_exit(void) /* Destructor */
{
	cdev_del(&c_dev);
	device_destroy(cl, first);
	class_destroy(cl);
	unregister_chrdev_region(first, 1);
	printk(KERN_INFO "Alvida: ofcd unregistered");
}

module_init(ofcd_init);
module_exit(ofcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("Our First Character Driver");

Then, Shweta repeated the usual build with new test steps as follows:

  • Build the driver (.ko file) by running make.
  • Load the driver using insmod.
  • List the loaded modules using lsmod.
  • List the major number allocated using cat /proc/devices.
  • “null driver” specific experiments (Refer to Figure 10 for details).
  • Unload the driver using rmmod.
Figure 10: “null driver” experiments

Figure 10: “null driver” experiments

Summing up

Shweta was surely happy as all on her own she got a character driver written, which works same as the driver for the standard device file /dev/null. To understand what it means, check for yourself the <major, minor> tuple for /dev/null, and similarly also try out the echo and cat commands with it.

But one thing started bothering Shweta. She had got her own calls (my_open, my_close, my_read, my_write) in her driver, but how are they working so unusually unlike any regular file system calls. What’s so unusual? Whatever I write, I get nothing when read – isn’t that unusual, at least from regular file operations’ perspective. Any guesses on how would she crack this nut? Watch out for the next article.

Sixth Article >>

Notes:

  1. For using a fixed major number, you may use register_chrdev_region() instead of alloc_chrdev_region().
  2. Use kernel version >= 2.6.3x for the class_create() and the device_create() APIs to compile properly work as explained. As, before that version they have been rapidly evolving and changing.
  3. Kernel APIs (like class_create(), device_create()) which returns pointers, should be checked using IS_ERR macro instead of comparing with NULL, as NULL is zero (i.e. success and not an error). These APIs return negative pointers on error – error code from which could be extracted using PTR_ERR. See the usage in the above example.

Other References:

  1. Working of udev daemon
   Send article as PDF   

Mathematics made easy with minimal Octave

This fifth article of the mathematical journey through open source, introduces the minimal, octave can do for you.

<< Fourth Article

You wanted a non-programmers way of doing mathematics and there you go – octave. Sounds like something to do with music, but in reality it is the name the octave author’s chemical engineering professor, who was well know for his ‘back of the envelope’ calculations.

All of bench calculator!!!

All the operations of bench calculator are just a subset of octave. So, no point going round the wheel again. Whatever operations can be done in bc – arithmetic, logical, relational, conditional – all can be done with equal ease in octave. As in bc, it can as well be used as a mathematical programming language. Ha! Then, why did we waste time on learning bc, we could have straight away come to octave. Yes! Yes! Except one thing – the precision-less-ness. We can’t get that in octave. If we need that, we would have to go back to bc. Okay! Fine. So, what with octave – we start with N-dimensions, what else. Hey don’t worry! In simple language, I mean vectors & matrices.

Getting Started

Command: Typing ‘octave‘ on the shell brings up the octave‘s interactive shell. Type ‘quit’ or Control-D to exit. The interactive shell starts with a welcome message. As in bc, we pass option -q for it to not show. Additionally, we’ll use the -f option for it to not pick any local start-up scripts (if any). So, for our examples, the command would look like ‘octave -qf‘ to start octave with an interactive shell.

Prompts & Results: Input prompt is typically denoted by ‘octave:X>‘, where X just a number showing the command count. Valid results are typically shown with ‘<variable> = ‘, or ‘ans = ‘ or ‘=> ‘, and then the command count incremented for the next input. Errors cause error messages to be printed and then octave brings back the input prompt without incrementing the command count. Putting a semicolon (;) at the end of an statement, suppresses it result (return value) to be displayed.

Comments could start with # or %. For block comments, #{ … }# or %{ … }% can be used.

Detailed topic help could be obtained using ‘help <topic>’ or complete documentation using ‘doc’ on the octave shell.

All in a go:

$ octave -qf
octave:1> # This is a comment
octave:1> pi # Built-in constant
ans =  3.1416
octave:2> e # Built-in constant again
ans =  2.7183
octave:3> i # Same as j – the imaginary number
ans =  0 + 1i
octave:4> x = 3^4 + 2*30;
octave:5> x
x =  141
octave:6> y
error: `y' undefined near line 6 column 1
octave:6> doc # Complete doc; Press 'q' to come back
octave:7> help plot # Help on plot
octave:8> A = [1 2 3; 4 5 6; 7 8 9] # 3x3 matrix
A =

   1   2   3
   4   5   6
   7   8   9

octave:9> quit

Matrices with a Heart

Yes, we already saw a matrix in creation. It can also be created through multiple lines. Octave continues waiting for further input by prompting just ‘>’. Checkout below for creating the 3×3 magic square matrix in the same way, followed by various other interesting operations:

$ octave -qf
octave:1> M = [ # 3x3 magic square
> 8 1 6
> 3 5 7
> 4 9 2
> ]
M =

   8   1   6
   3   5   7
   4   9   2

octave:2> B = rand(3, 4); # 3x4 matrix w/ randoms in [0 1]
octave:3> B
B =

   0.068885   0.885998   0.542059   0.797678
   0.652617   0.904360   0.036035   0.737404
   0.043852   0.579838   0.709194   0.053118

octave:4> B' # Transpose of B
ans =

   0.068885   0.652617   0.043852
   0.885998   0.904360   0.579838
   0.542059   0.036035   0.709194
   0.797678   0.737404   0.053118

octave:5> A = inv(M) # Inverse of M
A =

   0.147222  -0.144444   0.063889
  -0.061111   0.022222   0.105556
  -0.019444   0.188889  -0.102778

octave:6> M * A # Should be identity, at least approx.
ans =

   1.00000   0.00000  -0.00000
  -0.00000   1.00000   0.00000
   0.00000   0.00000   1.00000

octave:7> function rv = psine(x) # Our phase shifted sine
>
> rv = sin(x + pi / 6);
>
>  endfunction 
octave:8> x = linspace(0, 2*pi, 400); # 400 pts from 0 to 2*pi
octave:9> plot(x, psine(x)) # Our function's plot
octave:10> polar(x, 10 * (1 - sin(x)), 'm*') # bonus Heart
octave:11> quit

Figure 1 shows the plot window which pops up by the command # 9 – plot.

Figure 2 is the bonus magenta heart of stars (*) from polar coordinates draw command # 10 – polar.

Figure 1: Plot of our 30° phase shifted sine

Figure 1: Plot of our 30° phase shifted sine

Figure 2: The Heart

Figure 2: The Heart

What next?

With ready-to-go level of introduction to octave, we are all set to explore it the fun way. What fun? Next one left to your imagination. And as we move on, we would take up one or more fun challenge(s), and try to see, how we solve it using octave.

Sixth Article >>

   Send article as PDF   

Linux Character Drivers

This fourth article, which is part of the series on Linux device drivers, deals with the various concepts of character drivers and their implementation.

<< Third Article

Shweta at her hostel room in front of her PC, all set to explore the characters of Linux character drivers, before it is being taught in the class. She recalled the following lines from professor Gopi’s class: “… today’s first driver would be the template to any driver you write in Linux. Writing any specialized advanced driver is just a matter of what gets filled into its constructor & destructor. …”. With that, she took out the first driver code, and popped out various reference books to start writing a character driver on her own. She also downloaded the on-line “Linux Device Drivers” book by Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman from http://lwn.net/Kernel/LDD3/. Here follows the summary from her various collations.

W’s of character drivers

We already know what are drivers and why we need them. Then, what is so special about character drivers? If we write drivers for byte-oriented operations or in the C-lingo the character-oriented operations, we refer to them as character drivers. And as the majority of devices are byte-oriented, the majority of device drivers are character device drivers. Take for example, serial drivers, audio drivers, video drivers, camera drivers, basic I/O drivers, …. In fact, all device drivers which are neither storage nor network device drivers are one form or the other form of character drivers. Let’s look into the commonalities of these character drivers and how Shweta wrote one of them.

Figure 7: Character driver overview

Figure 7: Character driver overview

The complete connection

As shown in Figure 7, for any application (user space) to operate on a byte-oriented device (hardware space), it should use the corresponding character device driver (kernel space). And the character driver usage is done through the corresponding character device file(s), linked to it through the virtual file system (VFS). What it means is that an application does the usual file operations on the character device file – those operations are translated to the corresponding functions into the linked character device driver by the VFS – those functions then does the final low level access to the actual devices to achieve the desired results. Note that though the application does the usual file operations, their outcome may not be the usual ones. Rather, they would be as driven by the corresponding functions in the device driver. For example, a read followed by a write may not fetch what has been written into, unlike in the case of regular files. Note that this is the usual expected behaviour for device files. Let’s take an audio device file as an example. What we write into it is the audio data we want to playback, say through a speaker. However, the read would get us the audio data we are recording, say through a microphone. And the recorded data need not be the played back data.

In this complete connection from application to the device, there are four major entities involved:

  1. Application
  2. Character device file
  3. Character device driver
  4. Character device

And the interesting thing is that, all of these can exist independently on a system, without the other being there. So, mere existence of these on a system doesn’t mean they are linked to form the complete connection. Rather, they need to be explicitly connected. Application gets connected to a device file by invoking open system call on the device file. Device file(s) are linked to the device driver by specific registrations by the driver. And the device driver is linked to a device by its device-specific low-level operations. Thus, forming the complete connection. With this, note that the character device file is not the actual device but just a placeholder for the actual device.

Major & minor number

Connection between the application and the device file is based on the name of the device file. However, the connection between the device file and the device driver is based on the number of the device file, not the name. This allows a user-space application to have any name for the device file, and enables the kernel-space to have trivial index-based linkage between the device file & the device driver. This device file number is more commonly referred as the <major, minor> pair, or the major & minor numbers of the device file. Earlier (till kernel 2.4), one major number was for one driver, and the minor number used to represent the sub-functionalities of the driver. With kernel 2.6, this distinction is no longer mandatory – there could be multiple drivers under same major number but obviously with different minor number ranges. However, this is more common with the non-reserved major numbers and standard major numbers are typically preserved for single drivers. For example, 4 for serial interfaces, 13 for mice, 14 for audio devices, …. The following command would list the various character device files on your system:

$ ls -l /dev/ | grep “^c”

<major, minor> related support in kernel 2.6

Type: (defined in kernel header <linux/types.h>)

dev_t // contains both major & minor numbers

Macros: (defined in kernel header <linux/kdev_t.h>)

MAJOR(dev_t dev) // extracts the major number from dev
MINOR(dev_t dev) // extracts the minor number from dev
MKDEV(int major, int minor) // creates the dev from major & minor

Connecting the device file with the device driver involves two steps:

  1. Registering for the <major, minor> range of device files
  2. Linking the device file operations to the device driver functions

First step is achieved using either of the following two APIs: (defined in kernel header <linux/fs.h>)

int register_chrdev_region(dev_t first, unsigned int cnt, char *name);
int alloc_chrdev_region(
	dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);

First API registers the cnt number of device file numbers starting from first, with the name. Second API dynamically figures out a free major number and registers the cnt number of device file numbers starting from <the free major, firstminor>, with the name. In either case, the /proc/devices kernel window lists the name with the registered major number. With this information, Shweta added the following into the first driver code.

#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t first; // Global variable for the first device number

In the constructor, she added:

int ret;

if ((ret = alloc_chrdev_region(&first, 0, 3, "Shweta")) < 0)
{
	return ret;
}
printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

In the destructor, she added:

unregister_chrdev_region(first, 3);

Putting it all together, it becomes:

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t first; // Global variable for the first device number

static int __init ofcd_init(void) /* Constructor */
{
	int ret;

	printk(KERN_INFO "Namaskar: ofcd registered");
	if ((ret = alloc_chrdev_region(&first, 0, 3, "Shweta")) < 0)
	{
		return ret;
	}
	printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));
	return 0;
}

static void __exit ofcd_exit(void) /* Destructor */
{
	unregister_chrdev_region(first, 3);
	printk(KERN_INFO "Alvida: ofcd unregistered");
}

module_init(ofcd_init);
module_exit(ofcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("Our First Character Driver");

Then, Shweta repeated the usual steps, she learnt for the first driver

  • Build the driver (.ko file) by typing make
  • Load the driver using insmod
  • List the loaded modules using lsmod
  • Unload the driver using rmmod

Summing up

Additionally, before unloading the driver, she peeped into the kernel window /proc/devices to look for the registered major number with the name “Shweta” using cat /proc/devices. It was right there. But she couldn’t find any device file created under /dev with the same major number. So, she created them by hand using mknod, and then tried reading & writing those. Figure 8 shows all these. Please note that the major number “250” may vary from system to system based on the availability. Figure 8 also shows the results, Shweta got from reading & writing one of the device files. That reminded her that the second step for connecting the device file with the device driver – “Linking the device file operations to the device driver functions” is not yet done. She realized that she needs to dig further information to complete this step and also to figure out the reason for the missing device files under /dev. We shall continue further in our next article, to figure out what more is Shweta learning and how is she going ahead with her first character driver.

Figure 8: Character device file experiments

Figure 8: Character device file experiments

Fifth Article >>

   Send article as PDF   

Getting Recursive with Bench Calculator

This fourth article of the mathematical journey through open source, takes you through the recursive functional power of bench calculator.

<< Third Article

Equipped with the fundamentals of programming functions, we are all set to jump into the spiral of recursion. Frankly speaking recursion is not a spiral, as many would like to see it as. It is more to do with the way our brain works. So, let’s look into it as our brain suggests. Any recursion fundamentally has two parts: the recursive relation and the termination condition(s). For mathematics, recursion is basically to do with functions.

Recursive relation

A function when expressed or defined using the function itself, but of lower order, we refer that as a recursive relation. Typical lower order examples would be f(n) expressed in f(n – 1), f(n – 2), … where n is an integer; f(x) expressed in f(x / 2), … where x is a real number. In case of mathematics, a recursive relation is also called a recurrence relation. Here goes some examples:

Factorial: n! = n * (n – 1)! or fact(n) = n * fact(n – 1)
Fibonacci: fib(n) = fib(n – 1) + fib(n – 2)
Exponent: ex = (ex/2)2 or exp(x) = (exp(x/2))^2

Termination Condition

Now, the point is, as we all understand, if we try to expand these, they will continue for ever, unless we decide to stop at some decent condition. For example, n! = n * (n-1)! = n * (n-1) * (n-2)! = n * (n-1) * (n-2) * (n-3)! = … and so on. So, we decide and say that let’s stop it, when we reach 0, as we assume n to be positive. And hence, n! becomes n * (n-1) * (n-2) * … * 2 * 1 * 0!. And then we define 0! to be 1. This is called the termination condition. And, we would notice that, whenever there is a recursive relation, we would need one or more such termination conditions to stop the relation going forever. The number of termination conditions typically depend on the different lower orders, we have in the recursive relation. For example, factorial of n has just one lower order factorial of (n-1); exponent of x has again just one lower order exponent of (x/2); but Fibonacci of n has two lower orders Fibonacci of (n-1) and Fibonacci of (n-2). Hence, factorial and exponent would have one termination condition and Fibonacci would have two termination conditions.

Together in formation

Based on this understanding, the above recursive relations can be written, as follows:

For factorial:
fact(n) = n * fact(n – 1), for n > 0
fact(n) = 1, for n = 0
For Fibonacci:
fib(n) = fib(n – 1) + fib(n – 2), for n > 1
fib(n) = 1, for n = 1
fib(n) = 0, for n = 0
For exponent:
exp(x) = (exp(x/2))^2, for x > 0.001
exp(x) = 1 + x, for x <= 0.001

What is this 0.001 for the exponent? Note that x is a real number and if it is positive, dividing by 2, how ever many times would never reach absolute zero, though it would become smaller and smaller approaching zero. Hence, we would never be able to stop, if we put x > 0. So, we take an approximation, depending on expected accuracy of the result. And for this reason, we cannot put e(x) just equal to 1 for x < 0.001, but need to have some variability based on x, and hence 1 + x. Moreover, as it makes sense to have x take all real values (positive, zero, and negative), the conditions could be further enhanced by replacing x by its absolute value. So, for exponent,

exp(x) = (exp(x/2))^2, for abs(x) > 0.001
exp(x) = 1 + x, for abs(x) <= 0.001

Together in action

Given that we have these mathematical formulations, putting them down into recursive functions of a programming language is a mere mechanical task. Here, it goes – all three in a single file recursion.bc:

define fact(n) {
    if (n <= 0) # < to avoid -ve value cases
        return 1
    return (n * fact(n - 1)) 
}
define fib(n) {
    if (n <= 0) # < to avoid -ve value cases
        return 0
    if (n == 1)
        return 1
    return (fib(n - 1) + fib(n - 2))
}
define abs(x) {
    if (x >= 0)
        return x
    return -x
}
define exp(x) {
    if (abs(x) <= 0.001)
        return (1 + x)
    return (exp(x/2) * exp(x/2))
}

There execution and usage is demonstrated here:

$ bc -ql recursion.bc
fact(0)
1
fact(5)
120
fib(1)
1
fib(2)
1
fib(3)
2
fib(4)
3
fib(5)
5
fib(6)
8
exp(0)
1
exp(1) # Shall be an approximation
2.71695572946643553835
e(1)
2.71828182845904523536
quit # Get out
$

Summing up

With all these, we have pretty much explored all the functionalities of the bench calculator, and if you are a programming geek, you may even do the next level of stuff with these. Next level stuff? Yes, I mean jumping to the next dimension, vectors, etc using arrays of bc. Aha! But isn’t there a way for non-programmers? Yes, there is octave.

Fifth Article >>

   Send article as PDF   

Kernel C Extras in a Linux Driver

This third article, in the series on Linux device drivers deals with the kernel’s message logging,
and kernel-specific GCC extensions.

<< Second Article

Enthused by how Pugs impressed professor Gopi, in the last class, Shweta decided to do something similar. And there was already an opportunity – finding out where has the output of printk gone. So, as soon as she entered the lab, she got hold of the best located system, logged into it, and took charge. Knowing her professor pretty well, she knew that there would be a hint for the finding, from the class itself. So, she flashed back what all the professor taught, and suddenly remembered the error output demonstration from “insmod vfat.ko” – dmesg | tail. She immediately tried that and for sure found out the printk output, there. But how did it come here? A tap on her shoulder brought her out of the thought. “Shall we go for a coffee?”, proposed Pugs. “But I need to …”. “I know what you are thinking about.”, interrupted Pugs. “Let’s go, yaar. I’ll explain you all about dmesg”.

Kernel’s message logging

On the coffee table, Pugs began:

As far as parameters are concerned, printf & printk are same, except that when programming for the kernel we don’t bother about the float formats of %f, %lf & their likes. However unlike printf, printk is not destined to dump its output on some console. In fact, it cannot do so, as it is something which is in the background, and executes like a library, only when triggered either from the hardware space or the user space. So, then where does printk print? All the printk calls, just put their contents into the (log) ring buffer of the kernel. Then, the syslog daemon running in the user space picks them for final processing & redirection to various devices, as configured in its configuration file /etc/syslog.conf.

You must have observed the out of place macro KERN_INFO, in the printk calls, in the previous article. That actually is a constant string, which gets concatenated with the format string after it, making it a single string. Note that there is no comma (,) between them – they are no two separate arguments. There are eight such macros defined in <linux/kernel.h> under the kernel source, namely:

#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			*/

Depending on these log levels (i.e. the first 3 characters in the format string), the syslog daemon in the user space redirects the corresponding messages to their configured locations – a typical one being the log file /var/log/messages for all the log levels. Hence, all the printk outputs are by default in that file. Though, they can be configured differently to say serial port (/dev/ttyS0) or say all consoles, like what happens typically for KERN_EMERG. Now, /var/log/messages is buffered & contain messages not only from the kernel but also from various daemons running in the user space. Moreover, the /var/log/messages most often is not readable by a normal user, and hence a user-space utility ‘dmesg‘ is provided to directly parse the kernel ring buffer and dump it on the standard output. Figure 6 shows the snippets from the two.

Figure 6: Kernel's message logging

Figure 6: Kernel’s message logging

Kernel-specific GCC extensions

With all these Shweta got frustrated, as she wanted to find all these by her own, and then do a impression in the next class – but all flop. Pissed off, she said, “So as you have explained all about printing in kernel, why don’t you tell about the weird C in the driver as well – the special keywords __init, __exit, etc.”

These are not any special keywords. Kernel C is not any weird C but just the standard C with some additional extensions from the C compiler gcc. Macros __init and __exit are just two of these extensions. However, these do not have any relevance in case we are using them for dynamically loadable driver, but only when the same code gets built into the kernel. All the functions marked with __init get placed inside the init section of the kernel image and all functions marked with __exit are placed inside the exit section of the kernel image, automatically by gcc, during kernel compilation. What is the benefit? All functions with __init are supposed to be executed only once during boot-up, till the next boot-up. So, once they are executed during boot-up, kernel frees up RAM by removing them by freeing up the init section. Similarly, all functions in exit section are supposed to be called during system shutdown. Now, if system is shutting down anyway, why do you need to do any cleanups. Hence, the exit section is not even built into the kernel – another cool optimization.

This is a beautiful example of how kernel & gcc goes hand-in-hand to achieve lot of optimizations and many other tricks – we could see others, as we go along. And that is why Linux kernel can be compiled only using gcc-based compilers – a close knit bond.

Kernel function’s return guidelines

While returning from coffee, Pugs started all praises for the OSS & its community. Do you know why different individuals are able to come together and contribute excellently without any conflicts – moreover in a project as huge as Linux? There are many reasons. But definitely, one of the strong reasons is, following & abiding by the inherent coding guidelines. Take for example the guideline for returning values from a function in kernel programming.

Any kernel function needing error handling, typically returns an integer-like type and the return value again follows a guideline. For an error, we return a negative number – a minus sign appended with a macro included through the kernel header <linux/errno.h>, that includes the various error number headers under the kernel sources, namely <asm/errno.h>, <asm-generic/errno.h>, <asm-generic/errno-base.h>. For success, zero is the most common return value, unless there is some additional information to be provided. In that case, a positive value is returned, the value indicating the information like number of bytes transferred.

Kernel C = Pure C

Once back into the lab, Shweta remembered their professor mentioning that no /usr/include headers can be used for kernel programming. But Pugs said that kernel C is just standard C with some gcc extensions. Why this conflict? Actually this is not a conflict. Standard C is just pure C – just the language. The headers are not part of it. Those are part of the standard libraries built in C for C programmers, based on the concept of re-using code. Does that mean, all standard libraries and hence all ANSI standard functions are not part of ‘pure’ C? Yes. Then, hadn’t it been really tough coding the kernel. Not for this reason. In reality, kernel developers have developed their own needed set of functions, and they are all part of the kernel code. printk is just one of them. Similarly, many string functions, memory functions, … are all part of the kernel source under various directories like kernel, ipc, lib, … and the corresponding headers under include/linux directory.

“O ya! That is why we need to have kernel source for building a driver”, affirmed Shweta. “If not the complete source, at least the headers are a must. And that is why we have separate packages to install complete kernel source or just the kernel headers”, added Pugs. “In the lab, all the sources are setup. But if I want to try out drivers on my Linux system at my hostel room, how do I go about it?” asked Shweta. “Our lab have Fedora, where the kernel sources typically get installed under /usr/src/kernels/<kernel_version> unlike the standard place /usr/src/linux. Lab administrators must have installed it using command line ‘yum install kernel-devel‘. I use Mandriva and installed the kernel sources using ‘urpmi kernel-source‘, replied Pugs. “But, I have Ubuntu”. “Okay!! For that just use apt-get install – possibly, ‘apt-get install linux-source‘”.

Summing up

Lab timings were just getting over. Suddenly, Shweta put out her curiosity – “Hey Pugs! What is the next topic we are going to learn in our Linux device drivers class?”. “Hmmm!! Most probably character drivers”. With this information, Shweta hurriedly packed up her bag & headed towards her room to setup the kernel sources and try out the next driver on her own. “In case you are stuck up, just give me a call. I’ll be there”, called up Pugs from the behind with a smile.

Fourth Article >>

Notes:

  1. The default syslog file /var/log/messages may vary from distro to distro. For example, in the latest Ubuntu distros, it is /var/log/syslog.

Other References:

  1. Another possible pointer to the missing /var/log/messages in Ubuntu
   Send article as PDF