Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does size always = 4096 in Linux character driver read call?

I've been working my way through the Linux char driver examples on the web but run across a behavior that I can't explain.

static ssize_t my_read(struct file *f, char __user *user_buf, size_t cnt, loff_t* off)
{
   printk( KERN_INFO "Read called for %zd bytes\n", cnt );
   return cnt;
}

The message always indicates that cnt=4096 bytes regardless of what the number of bytes specified in the user space call to read (e.g..

[11043.021789] Read called for 4096 bytes

However, the user space read calls

retval = fread(_rx_buffer, sizeof(char), 5, file_ptr);
printf( "fread returned %d bytes\n", retval );

The output from user space is

fread returned 5 bytes.

How is it that the value of the size in my_read is always 4096 but the value from fread indicates 5 ? I know there's something I'm missing but not sure what...

like image 470
DnRng Avatar asked Nov 08 '13 19:11

DnRng


People also ask

What does 4096 mean in Linux?

A directory is just a special file which contains an array of filenames and inode numbers. When the directory was created, the file system allocated 1 inode to the directory with a "filename" (dir name in fact). The inode points to a single data block (minimum overhead), which is 4096 bytes.

What is Character driver in Linux?

Character device drivers normally perform I/O in a byte stream. Examples of devices using character drivers include tape drives and serial ports. Character device drivers can also provide additional interfaces not present in block drivers, such as I/O control (ioctl) commands, memory mapping, and device polling.

Why is the size of a directory usually small?

Size of directory: The size of directory in UNIX is usually small because the size needed to store the meta data about the files that are stored in the directory is usually small. Therefore the size allocated to the directory is small usually 4096 kb.


1 Answers

Try read(2) (in unistd.h) and it should output 5 characters. When using libc (fread(3), fwrite(3), etc.), you're using the internal libc buffer, which is usually the size of a page (which is almost always 4 kiB).

I believe that the first time you call fread() for 5 bytes, libc does an internal read() of 4096 bytes and the following fread() will simply return bytes libc already has in the buffer associated with the FILE structure you use. Until you reach 4096. The 4097th byte will issue another read of 4096 bytes and so on.

This also happens when you write, for example when using printf(), which is just fprintf() with stdout() as its first argument. libc won't call write(2) directly, but put your stuff in its internal buffer instead (also of 4096 bytes). It will flush if you call

fflush(stdout);

yourself, or anytime it finds the byte 0x0a (newline in ASCII) within the bytes sent.

Try it, you shall see:

#include <stdio.h> /* for printf() */
#include <unistd.h> /* for sleep() */

int main(void) {
    printf("the following message won't show up\n");
    printf("hello, world!");
    sleep(3);
    printf("\nuntil now...\n");

    return 0;
}

This will work however (not using libc's buffering):

#include <stdio.h> /* for printf() */
#include <unistd.h> /* for sleep(), write(), and STDOUT_FILENO */

int main(void) {
    printf("the following message WILL show up\n");
    write(STDOUT_FILENO, "hello!", 6);
    sleep(3);
    printf("see?\n");

    return 0;
}

STDOUT_FILENO is the default file descriptor for the standard output (1).

Flushing every time there's a newline is essential for the user of a terminal to see messages instantly, and is also helpful for per-line processing, which is done a lot in a Unix environment.

So, even if libc uses read() and write() syscalls directly to fill and flush its buffers (and by the way the Microsoft implementation of the C standard library must be using Windows stuff, probably ReadFile and WriteFile), those syscalls absolutely do not know libc. This leads to interesting behaviours when using both:

#include <stdio.h> /* for printf() */
#include <unistd.h> /* for write() and STDOUT_FILENO */

int main(void) {
    printf("1. first message (flushed now)\n");
    printf("2. second message (without flushing)");
    write(STDOUT_FILENO, "3. third message (flushed now)", 30);
    printf("\n");

    return 0;
}

which outputs:

1. first message (flushed now)
3. third message (flushed now)2. second message (without flushing)

(third before second!).

Also, note that you can turn off libc's buffering with setvbuf(3). Example:

#include <stdio.h> /* for setvbuf() and printf() */
#include <unistd.h> /* for sleep() */

int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("the following message WILL show up\n");
    printf("hello!");
    sleep(3);
    printf("see?\n");

    return 0;
}

I never tried, but I guess you could do the same with the FILE* you get when fopen()ing your character device and disable I/O buffering for this one:

FILE* fh = fopen("/dev/my-char-device", "rb");
setvbuf(fh, NULL, _IONBF, 0);
like image 111
eepp Avatar answered Oct 22 '22 00:10

eepp