Why is size always = 4096 in a Linux character character read call?

I am working on examples of Linux char drivers on the Internet, but am looking at behavior that I cannot 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 how many bytes specified in the user space request to read (for example,

 [11043.021789] Read called for 4096 bytes 

However, user space reads calls

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

Exit user space

 fread returned 5 bytes. 

How is it that the size value in my_read always 4096, but the value from fread indicates 5? I know that something is missing me, but I do not know that ...

+6
source share
1 answer

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

I believe that when you first call fread for 5 bytes, libc does an internal read of 4096 bytes, and the next fread just returns bytes. Libc already has the structure you use in the buffer associated with the FILE . Until you reach 4096. The 4097th byte produces another read of 4096 bytes, etc.

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

 fflush(stdout); 

alone or at any time when byte 0x0a is sent (new line in ASCII).

Try it, you will see:

 #include <stdio.h> #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; } 

However, this will work (without using libc buffering):

 #include <stdio.h> #include <unistd.h> /* for sleep() and write() */ int main(void) { printf("the following message WILL show up\n"); write(0, "hello!", 6); sleep(3); printf("see?\n"); return 0; } 

Of course, you should know that 0 is the default file descriptor for standard output.

Wetting every time a new line is important for the terminal user to instantly see messages, and also useful for processing in each line, which is a lot done in the Unix environment.

Thus, even if libc uses read and write syscalls directly to fill and flush its buffers (and by the way, the Microsoft implementation of the standard C library should use Windows stuff, possibly ReadFile and WriteFile ), these system calls absolutely do not know libc. This leads to interesting behavior when using both:

 #include <stdio.h> #include <unistd.h> /* for write() */ int main(void) { printf("1. first message (flushed now)\n"); printf("2. second message (without flushing)"); write(0, "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 - second!).

Also note that you can disable libc buffering with setvbuf(3) . Example:

 #include <stdio.h> #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 have never tried, but I think you could do the same with FILE* , which you get when fopen with your character device and disable I / O buffering for this:

 FILE* fh = fopen("/dev/my-char-device", "rb"); setvbuf(fh, NULL, _IONBF, 0); 
+10
source

Source: https://habr.com/ru/post/957704/


All Articles