- Linux c file operations
- Notes [1] This is by convention. When writing a driver, it’s OK to put the device file in your current directory. Just make sure you place it in /dev for a production driver Источник Linux c file operations Open function Open a file and returnFile descriptor, Follow-up read, write and other operations need this return value Header file and original function #include #include #include int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); Const char * pathname — Open the file address (or use «./file») int flags — open mode Mode_t Mode — Controller (0600 is administrator) Close function Close the open function, with Open, Open must close! ! ! Header file and original function int close(int fd); INT FD — File Descriptor (Open’s Return Value) Write function In the specified content, the function returns value is the size of the entry string. Header file and original function #include ssize_t write(int fd, const void *buf, size_t count); INT FD — file descriptor to be written const void * buf — content address to be written size_t count — want to write to the size of the file Read function reads the contents of the file and records, the function returns the value of the string size that is actually read. Header file and original function ssize_t read(int fd, void *buf, size_t count); INT FD — the file descriptor to read void * buf — getting read content stored size_t count — want to read the file size Lseek function The cursor position within the file. Header file and original function off_t lseek(int fd, off_t offset, int whence); INT FD — I want to move the file descriptor of the cursor OFF_T Offset — Offset starting from the cursor position int imce — mobile cursor to special location CP function ideas CP file 1 file 2 MAIN function gives call content int main(int argc,char **argv) is enough to detect the number of files in the file argv to read files Open Argv [1] read file content Open Argv [2] to write to read content Close two files Read the archive, modify the archive Use Open to open the file you want to modify Read the file and put the content into the readbuf Use strstr () to find the modified content Offset the pointer to modify the content Replace the pointer Move to the file header Write the contents of the readbuf to the read file Close read file Standard C library Transplantable high relative to Linux Fopen function Open file in the C library, the return value of the value is a pointer. FILE *fopen(const char *pathname, const char *mode); Const char * pathname — Open the file address (or «./a.out») const char * mode — Open mode Fclose function The closed file in the C library. int fclose(FILE *stream); File * stream — File Descriptor to close the file FWRITE function The write function in the C library, the number of returns is actually written size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); Const void * Ptr — String address to be written size_t size — each write size size_t nmemb — write time file * stream — file modifier writing files Write size * write time = string length FREAD function The read function in the C library, the number of returns is actually read. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); Void * Ptr — Read the content storage location size_t size — each write size size_t nmemb — write time file * stream — read file modifier FSeek function Change Cursor Position Function in C Library int fseek(FILE *stream, long offset, int whence); File * stream — Document to change the position of the cursor long offset — start offset from the cursor int imce — change the cursor position FPUTC function C library print strings int fputc(int c, FILE *stream); INT C — content you need to print (you can use a string, you need to offset when printing) file * stream — file descriptor of the printed target file FGETC function C lib library gets the content int fgetc(FILE *stream); File * stream — File descriptor to get file FeOf function The end symbol of the file in the C library. If the file is completed, it returns 0 value if the file is not ended. int feof(FILE *stream); File * stream — File descriptor to get file Источник Folder operation under linux The system provides the following library functions to manipulate folders: Related API functions opendir(3) DIR *opendir(const char *name); closedir(3) int closedir(DIR *dirp); readdir(3) struct dirent *readdir(DIR *dirp); Code example: diros.c Results of the The realization principle of file redirection File redirection is to relocate the flow of files. To complete the file redirection function, the file descriptor needs to be copied. Related API functions int dup(int oldfd); dup2(2) int dup2(int oldfd, int newfd); Code example direct.c Results of the File lock There are two types of file locks: Mandatory lock Recommended lock According to the mutual exclusion of locks, it can be divided into read locks (shared locks) and write locks (mutexes). Related usage Use fcntl to complete the file lock function: Code example Two processes simultaneously add a read lock to a file. processA.c processB.c Results of the: Read lock result Modify the B process to try to write lock: It is found that process B will block and wait while process A is in use. The relationship between library functions and system call functions Library functions: (buffer files) fopen, fclose, fputc, fgetc. System call: (non-buffered file) open, close, write, read. Specific instructions fopen(3) allocates a piece of memory of the structure FILE, there is a member variable _fileno in the FILE type, which is used to save the file descriptor. In addition, a cache is allocated, and the read and write operations of the library function to the file are for this cache; open(2) is called to open the corresponding file. fgetc(3) When fgetc is called, it is mainly for the cache to obtain data from the cache. If there is data in the cache, the obtained data will be returned immediately. If there is no data in the cache, call read(2), and the file descriptor is passed in with the _fileno parameter of the FILE type. Get data from the system to the cache, and then fgetc(3) returns the obtained data. fputc(3) When calling fputc, if the write buffer has space, write the characters directly to the buffer; if the write buffer is full, call write(2) to write the contents of the write buffer to the file. Clear the cache, and then write the characters to the write cache. fclose(3) First clear the contents of the cache to the file, then call close(2) to close the file descriptor, and then release the cache space opened by fopen(3). Code example file.c Results of the linux process basics The difference between program and process: The program is static, stored on the disk, and is a collection of instructions. A process is an instance of a program running, and a process will be generated once a program runs. Each process has its own pid, and each process has its own PCB. Process is the basic unit of resource allocation. In the Linux operating system, the direct relationship between processes is the axe relationship or the brother relationship. All user-level processes form a tree. Use pstree to view this tree. Init is the root of this tree, which is process No. 1 (the first process of the user process). ). Related API How to create a new process? Use the system call fork (2) to create a new process. pid_t fork(void); Источник
- Linux c file operations
- Standard C library
- Folder operation under linux
- Related API functions
- opendir(3)
- closedir(3)
- readdir(3)
- Code example:
- The realization principle of file redirection
- Related API functions
- dup2(2)
- Code example
- File lock
- Related usage
- Code example
- The relationship between library functions and system call functions
- Specific instructions
- Code example
- linux process basics
- Related API
Linux c file operations
The file_operations structure is defined in linux/fs.h , and holds pointers to functions defined by the driver that perform various operations on the device. Each field of the structure corresponds to the address of some function defined by the driver to handle a requested operation.
For example, every character driver needs to define a function that reads from the device. The file_operations structure holds the address of the module’s function that performs that operation. Here is what the definition looks like for kernel 2.4.2 :
Some operations are not implemented by a driver. For example, a driver that handles a video card won’t need to read from a directory structure. The corresponding entries in the file_operations structure should be set to NULL .
There is a gcc extension that makes assigning to this structure more convenient. You’ll see it in modern drivers, and may catch you by surprise. This is what the new way of assigning to the structure looks like:
However, there’s also a C99 way of assigning to elements of a structure, and this is definitely preferred over using the GNU extension. The version of gcc I’m currently using, 2.95 , supports the new C99 syntax. You should use this syntax in case someone wants to port your driver. It will help with compatibility:
The meaning is clear, and you should be aware that any member of the structure which you don’t explicitly assign will be initialized to NULL by gcc.
A pointer to a struct file_operations is commonly named fops .
Each device is represented in the kernel by a file structure, which is defined in linux/fs.h . Be aware that a file is a kernel level structure and never appears in a user space program. It’s not the same thing as a FILE , which is defined by glibc and would never appear in a kernel space function. Also, its name is a bit misleading; it represents an abstract open `file’, not a file on a disk, which is represented by a structure named inode .
A pointer to a struct file is commonly named filp . You’ll also see it refered to as struct file file . Resist the temptation.
Go ahead and look at the definition of file . Most of the entries you see, like struct dentry aren’t used by device drivers, and you can ignore them. This is because drivers don’t fill file directly; they only use structures contained in file which are created elsewhere.
Adding a driver to your system means registering it with the kernel. This is synonymous with assigning it a major number during the module’s initialization. You do this by using the register_chrdev function, defined by linux/fs.h .
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
where unsigned int major is the major number you want to request, const char *name is the name of the device as it’ll appear in /proc/devices and struct file_operations *fops is a pointer to the file_operations table for your driver. A negative return value means the registertration failed. Note that we didn’t pass the minor number to register_chrdev . That’s because the kernel doesn’t care about the minor number; only our driver uses it.
Now the question is, how do you get a major number without hijacking one that’s already in use? The easiest way would be to look through Documentation/devices.txt and pick an unused one. That’s a bad way of doing things because you’ll never be sure if the number you picked will be assigned later. The answer is that you can ask the kernel to assign you a dynamic major number.
If you pass a major number of 0 to register_chrdev , the return value will be the dynamically allocated major number. The downside is that you can’t make a device file in advance, since you don’t know what the major number will be. There are a couple of ways to do this. First, the driver itself can print the newly assigned number and we can make the device file by hand. Second, the newly registered device will have an entry in /proc/devices , and we can either make the device file by hand or write a shell script to read the file in and make the device file. The third method is we can have our driver make the the device file using the mknod system call after a successful registration and rm during the call to cleanup_module .
We can’t allow the kernel module to be rmmod ‘ed whenever root feels like it. If the device file is opened by a process and then we remove the kernel module, using the file would cause a call to the memory location where the appropriate function (read/write) used to be. If we’re lucky, no other code was loaded there, and we’ll get an ugly error message. If we’re unlucky, another kernel module was loaded into the same location, which means a jump into the middle of another function within the kernel. The results of this would be impossible to predict, but they can’t be very positive.
Normally, when you don’t want to allow something, you return an error code (a negative number) from the function which is supposed to do it. With cleanup_module that’s impossible because it’s a void function. However, there’s a counter which keeps track of how many processes are using your module. You can see what it’s value is by looking at the 3rd field of /proc/modules . If this number isn’t zero, rmmod will fail. Note that you don’t have to check the counter from within cleanup_module because the check will be performed for you by the system call sys_delete_module , defined in linux/module.c . You shouldn’t use this counter directly, but there are macros defined in linux/modules.h which let you increase, decrease and display this counter:
MOD_INC_USE_COUNT : Increment the use count.
MOD_DEC_USE_COUNT : Decrement the use count.
MOD_IN_USE : Display the use count.
It’s important to keep the counter accurate; if you ever do lose track of the correct usage count, you’ll never be able to unload the module; it’s now reboot time, boys and girls. This is bound to happen to you sooner or later during a module’s development.
The next code sample creates a char driver named chardev . You can cat its device file (or open the file with a program) and the driver will put the number of times the device file has been read from into the file. We don’t support writing to the file (like echo «hi» > /dev/hello ), but catch these attempts and tell the user that the operation isn’t supported. Don’t worry if you don’t see what we do with the data we read into the buffer; we don’t do much with it. We simply read in the data and print a message acknowledging that we received it.
Example 4-1. chardev.c
The system calls, which are the major interface the kernel shows to the processes, generally stay the same across versions. A new system call may be added, but usually the old ones will behave exactly like they used to. This is necessary for backward compatibility — a new kernel version is not supposed to break regular processes. In most cases, the device files will also remain the same. On the other hand, the internal interfaces within the kernel can and do change between versions.
The Linux kernel versions are divided between the stable versions (n.$ $.m) and the development versions (n.$ $.m). The development versions include all the cool new ideas, including those which will be considered a mistake, or reimplemented, in the next version. As a result, you can’t trust the interface to remain the same in those versions (which is why I don’t bother to support them in this book, it’s too much work and it would become dated too quickly). In the stable versions, on the other hand, we can expect the interface to remain the same regardless of the bug fix version (the m number).
There are differences between different kernel versions, and if you want to support multiple kernel versions, you’ll find yourself having to code conditional compilation directives. The way to do this to compare the macro LINUX_VERSION_CODE to the macro KERNEL_VERSION . In version a.b.c of the kernel, the value of this macro would be $2^<16>a+2^<8>b+c$. Be aware that this macro is not defined for kernel 2.0.35 and earlier, so if you want to write modules that support really old kernels, you’ll have to define it yourself, like:
Example 4-2. some title
#if LINUX_KERNEL_VERSION >= KERNEL_VERSION(2,2,0) #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c)) #endif
Of course since these are macros, you can also use #ifndef KERNEL_VERSION to test the existence of the macro, rather than testing the version of the kernel.
Notes
[1] |