Operating system leb5 experiment report

Posted by qazwsx on Sat, 22 Jan 2022 01:44:27 +0100

Experiment Name: Experiment 5: proc file system programming

Experimental purpose

  • 1. Familiar with Linux commands
  • 2. Familiar with system API and programming

Experimental content

In leb4, although we can obtain the family information of process number pid through the module with parameters, it is not convenient for us to obtain the family information of another process p2. We can only unload the module first, and then reload the module with the pid of process p2 as the parameter. In addition, if printk is used to generate output information, this information is mixed with other information of the system, which is not conducive to the automatic extraction and analysis of the program.
In this experiment, we still realize the function of leb4 subtask (2), but we do not use the mode of module parameters, but realize the user state and core state communication through the proc file system. The pid of the process is transmitted from the proc file, and the process family information is also accessed through the proc file.

Experimental environment

  1. VMware
  2. Fedora7

Experimental homework

We have come into contact with the basic concept of proc file system in leb4, and write a program to read the contents of several files, which has swept away the mystery of proc file system. Next, we will improve the shortcomings of Experiment 4, use kernel module programming to add several files in the / proc directory, and users can access the functions provided by the module by reading / writing these files. Through this experiment, we master a method of user state and core state communication.

example

procfs_example.c

#include<linux/init.h>
#include<linux/module.h>
#include<linux/proc_fs.h>
#include<linux/sched.h>
#include<asm/uaccess.h>

MODULE_LICENSE("GPL");

#define BUFSIZE 1024

static char global_buffer[BUFSIZE];
static struct proc_dir_entry *example_dir,*hello_file,*current_file,*symlink;

static int proc_read_current(char *page,char** start,off_t off, int count,int *eof,void *data)
{
	int len;
	len=sprintf(page,"current process-->name:%s gid:%d pid:%d\n",current->comm,current->gid,current->pid);//Output string to page	
	return len;
}

static int proc_read_hello(char* page,char **start,off_t off,int count,int *eof,void *data)
{
	int len;
	len=sprintf(page,"%s",global_buffer);//Output string to page
	return len;
}

static int proc_write_hello(struct file *file,const char* buffer,unsigned long count,void *data)
{
	int len;
	if(count>BUFSIZE-1)
		len=BUFSIZE-1;
	else
		len=count;
	if(copy_from_user(global_buffer,buffer,len))
		return -EFAULT;
	global_buffer[len]='\0';
	return len;

}

static int proc_init(void)
{
	example_dir=proc_mkdir("proc_test",NULL);
	example_dir->owner=THIS_MODULE;
	
	current_file=create_proc_read_entry("current",0444,example_dir,proc_read_current,NULL);
	
	current_file->owner=THIS_MODULE;
	
	hello_file=create_proc_entry("hello",0644,example_dir);
	strcpy(global_buffer,"hello\n");
	hello_file->read_proc=proc_read_hello;
	hello_file->write_proc=proc_write_hello;
	hello_file->owner=THIS_MODULE;
	
	symlink=proc_symlink("current_too",example_dir,"current");
	symlink->owner=THIS_MODULE;
	return 0;	
}

static void proc_exit(void)
{
	remove_proc_entry("current_too",example_dir);
	remove_proc_entry("hello",example_dir);
	remove_proc_entry("current",example_dir);
	remove_proc_entry("proc_test",NULL);
}

module_init(proc_init);
module_exit(proc_exit);


The above program is actually a kernel module, compiled and generated PROCFS according to the method introduced in Experiment 4_ example. Ko file, and then load it with insmod command. We know proc_ The init function will be called. proc_init first creates a subdirectory proc under the / proc directory_ Test, and then create two files current and hello in this directory. The program also creates a symbolic link file current_too, read current_ The effect of too is the same as reading current.

Operation diagram:
![](https://img-blog.csdnimg.cn/img_convert/a261f78ddbbe2e79ad0b5d5938dc9a1f.png#from=url&id=Ly47P&margin=[object Object]&originHeight=266&originWidth=544&originalType=binary&ratio=1&status=done&style=none)
Current is read-only. It displays the pid, gid and executable program name of the current process, so cat / proc / proc is executed for the first time_ Test / current is to run the cat command to generate process information, and then the process ends. And then execute cat /proc/proc_testlcurrent_too outputs the process information generated by running the cat command again, so the information is different between the two times. The Hello file is read / write, and its contents are initialized to "hello", see line 55 of the code.
Here we emphasize the relationship between module code and process again. Module is not a process, and it becomes a part of the kernel after loading. When the process accesses the kernel service, it may execute some code of the module. For example, the previous four command lines (three cat and one echo) all run part of the code of the module. Finally, you can uninstall the module, proc_ When the exit function is called, we will find that the proc directory and file created earlier have disappeared.

Core data structure of proc file

Unlike the disk file system, the information of the proc file system resides in memory, which means that the proc file system cannot occupy too much space. Two factors ensure this. One is that the proc file system itself is very small, and the other is that the contents of the proc files (even some proc directories) are dynamically generated according to the temporary needs of users, There is no need to permanently occupy memory.
The metadata stored in the memory of the proc file system is used in struct proc_dir_entry structure description, which can describe both directories and files, is defined as follows:

<proc_fs.h>
struct proc_dir_entry {
	unsigned int low_ino;	//inode number
	unsigned short namelen;
	const char *name;
	mode_t mode;
	nlink_t nlink;//Number of subdirectories and soft links
	uid_t uid;
	gid_t gid;
	loff_t size;
	const struct inode_operations *proc_iops;
	const struct file_operations *proc_fops;
	get_info_t *get_info;
	struct module *owner;
	struct proc_dir_entry *next, *parent, *subdir;
	void *data;
	read_proc_t *read_proc;
	write_proc_t *write_proc;
	atomic_t count; /* use count */ 
	int deleted; /* delete flag */ 
	kdev_t rdev; 
};

The proc file system can actually be regarded as that each node is proc_ dir_ The tree structure of entry. Each node saves the information of its parent node (parent member), child node (subdir linked list) and brother node (next member), which is convenient for maintenance and management.
For proc programmers, creating and initializing proc_ dir_ The work of entry can be basically completed with the help of kernel API, with only a small part of proc_ dir_ The member items of entry need to be manually controlled by programmers.

proc file system programming interface

The following describes several kernel functions that can request the kernel to create or delete files / directories in the proc file system. The prototype of these functions is in linux/proc_fs.h file.

(1) Create directory - proc_mkidr(

The function prototype is

struct proc_dir_entry*proc_mkdir(const char *name, struct proc_dir_entry *parent)

This function will create a directory named name and the parent directory is parent. If you want to create a subdirectory under the root directory, specify NULL as the parent. If the function creation fails, NULL is returned; otherwise, a new proc is returned_ dir_ The address of the entry entry.

(2) Create file - create_proc_entry()

The function prototype is

struct proc_dir_entry *create_proc_entry(const char*name, mode_t mode,struct proc_dir_entry *parent)

This function will create a proc file / directory with the name of name, file type and access permission of mode, and its parent directory of parent. If you want to create in the root directory of the proc file system, the specified parameter parent is NULL. If the function creation fails, NULL is returned; otherwise, a new proc is returned_ dir_ The address of the entry entry. Note that the created files and directories cannot be deleted with the rm and rmdir commands of the conventional file system, and can only be deleted with the commands described below
remove_proc_entry to delete.

(3) Create read-only file - create_proc_read_entry()

The function prototype is

struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode,structproc_dir_entry*base, read_proc_t *read_proc, void * data)

This function creates a read-only proc file. In fact, it simply calls create_proc_entry and will return the read of the structure_ The value of the proc field is set to read_proc, set the data field to data. If the function creation fails, NULL is returned; otherwise, a new proc is returned_ dir_ The address of the entry entry.

(4) Create symbolic link - proc_symlink()

The function prototype is

struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, char *dest)

This function creates a symbolic link file named name in the parent directory, and the target of the link is dest.

(5) Delete file / Directory - remove_proc_entry()

The function prototype is

void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

This function deletes the proc node named name in the parent directory. If the file to be deleted is in use, set proc_ dir_ The deleted flag in the entry structure. Otherwise, it will be deleted directly.

(6) Read / write file interface - read_proc and write_proc

It is not enough to only create a proc file. The file also has content. Either the kernel information is provided to the user through the file, or the user affects the kernel behavior by writing the file.
The simplest way to handle this is to provide the user with proc_ dir_ Read of entry structure_ Proc and write_proc member. Both functions are callback functions, that is, they will be called automatically when the file is read / written. usage
The prototype of the file reading interface function is as follows:

int (*read_proc)(char*page, char**start, off_t off, int count, int *eof, void *data);

In fact, there are three different implementations. Interested readers can check the kernel function proc_file_read source code implementation, from which you can get detailed information. The simplest implementation of the three methods is to ignore read in addition to the parameter page_ For all parameters of proc, write all contents of the file into the buffer pointed to by the parameter page, and then return the length of the file. This implementation must have a premise that the amount of data in the proc file is very small and will not exceed one page.
With / proc/proc_test/current corresponding read function proc_ read_ Take the implementation of current as an example, because it only reads the pid, gid and executable name of the current process, and the amount of information will never exceed one page, so the implementation method mentioned above can be adopted. sprintf is a core state function, which is declared in Linux / kernel H, but its usage is the same as that of standard library functions; Current is a macro that points to the task of the current process_ Struct, which requires the header file Linux / sched h.
The prototype of the write file interface function is as follows:

int (*write_proc)(struct file *file, const char *buffer,unsigned long count, void *data);

This function writes count bytes in the buffer starting from buffer to file. Data is private data and generally does not need to be concerned. Since buffer is generally a pointer to user space, it points to the buffer of user space. Therefore, copy should be called first_ from_ User copies the data into kernel space. Here are two kernel functions, copy_to_user and copy_from_user, its prototype is as follows:

unsigned long copy_to_user(void __user*to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to,const void _user*from, unsigned long count);

When using, include the header file ASM / uaccess h. The former copies the count bytes from the kernel space address from to the buffer pointed to by the user space pointer to, and the latter copies the count bytes from the user space address from to the buffer pointed to by the kernel space pointer to.
Example / proc / proc_ Write function Proc of test / hello file_ write_ hello is very simple. The hello file uses the buffer global_buffer saves data, so writing hello is just calling copy_from_user copies user status data to global_ Just a buffer. It is also worth noting that write_ There is no file offset in the parameters of proc, so the user state program calls the write function to / proc/proc_test/hello writes data at one time. If write is called multiple times, the data written later will overwrite the previous data.

experimental result

#include<linux/init.h>
#include<linux/module.h>
#include<linux/proc_fs.h>
#include<linux/sched.h>
#include<asm/uaccess.h>


MODULE_LICENSE("GPL");
//Parameter storage
static int fipid =1;
module_param(fipid,int,S_IRUGO);
//Process information pointer
struct task_struct *p,*xdp,*erp;
struct list_head *h;
struct pid * kpid;
#define BUFSIZE 1024

static char global_buffer[BUFSIZE];
static struct proc_dir_entry *example_dir,*hello_file,*current_file;

static int proc_read_current(char *page,char** start,off_t off, int count,int *eof,void *data)
{
	int len=0;
	p=find_task_by_pid(fipid);
	if(p->parent==NULL)
	{
		printk(KERN_ALERT "4\n");
		len=sprintf(page, "PID number:\t%d\n No parent process",p->pid);
	}
	else 
	{
		len+=sprintf(page, "PID number:\t%d\n Parent process PID number:\t%d\n Parent process name:\t%s\n",p->pid,p->parent->pid,p->parent->comm);
	}
	list_for_each(h,&p->sibling)
	{
		xdp=list_entry(h,struct task_struct,sibling);
		len+=sprintf(page+len, "Brother process PID number:	%d\n",xdp->pid);
		len+=sprintf(page+len, "Brother process program name:	%s\n",xdp->comm);
	}	
	list_for_each(h,&p->children)
	{
		erp=list_entry(h,struct task_struct,sibling);
		len+=sprintf(page+len,  "Child process PID number:	%d\n",erp->pid);
		len+=sprintf(page+len, "Child process program name:	%s\n",erp->comm);
	}
	return len;
}

static int proc_read_hello(char* page,char **start,off_t off,int count,int *eof,void *data)
{
	int len;
	len=sprintf(page,"%d",fipid);//Output string to page
	return len;
}

static int proc_write_hello(struct file *file,const char* buffer,unsigned long count,void *data)
{
	int len;
	if(count>BUFSIZE-1)
		len=BUFSIZE-1;
	else
		len=count;
	if(copy_from_user(global_buffer,buffer,len))
		return -EFAULT;
	global_buffer[len]='\0';
	printk(KERN_ALERT "12\n");
	fipid=simple_strtol(buffer,NULL,0);
	printk(KERN_ALERT "fpid :%d\n",fipid);
	return fipid;

}

static int proc_init(void)
{
	example_dir=proc_mkdir("proc_test",NULL);
	example_dir->owner=THIS_MODULE;
	
	hello_file=create_proc_entry("pid",0644,example_dir);
	strcpy(global_buffer,"1\n");
	hello_file->read_proc=proc_read_hello;
	hello_file->write_proc=proc_write_hello;
	hello_file->owner=THIS_MODULE;

	current_file=create_proc_read_entry("family",0444,example_dir,proc_read_current,NULL);
	current_file->owner=THIS_MODULE;
	

	
	return 0;	
}

static void proc_exit(void)
{

	remove_proc_entry("family",example_dir);
	remove_proc_entry("pid",example_dir);
	remove_proc_entry("proc_test",NULL);
}

module_init(proc_init);
module_exit(proc_exit);


Operation diagram:
![](https://img-blog.csdnimg.cn/img_convert/a9084290f5100a87b52602ecf9313725.png#from=url&id=hYI6S&margin=[object Object]&originHeight=43&originWidth=354&originalType=binary&ratio=1&status=done&style=none)
Check that the initial default pid is 1;
![](https://img-blog.csdnimg.cn/img_convert/9a0d8494eb8a49b1e5e2929b73023804.png#from=url&id=DVsTe&margin=[object Object]&originHeight=534&originWidth=594&originalType=binary&ratio=1&status=done&style=none)
The process information with pid 1 is also displayed in the family
![](https://img-blog.csdnimg.cn/img_convert/94715e69e0eba323bf48613fae55e973.png#from=url&id=ZWt10&margin=[object Object]&originHeight=138&originWidth=387&originalType=binary&ratio=1&status=done&style=none)
Input other existing pid numbers into the pid file, open the family again, and find that the information in it has changed.
Complete the experimental requirements and the experiment is over.

Experimental summary

Every experiment makes me learn something that I can't learn in ordinary class. Not necessarily how perfect my experiment can be, but I always devote myself to research and study. But I'm excited to finish every task. Generally speaking, my experiment has met the basic requirements of the teacher. To sum up, I have the following experience.
1. The network is really powerful. It will be a very efficient assistant in learning. Almost all the information can be found on the Internet. Because of this, I have browsed more than 30 relevant web pages (incomplete statistics) through the whole experiment. Of course, things on the Internet are very messy and miscellaneous. You should be able to learn to screen.
2. Dare to tackle tough problems, the more difficult the problem, the more challenging the psychology. In this way, we can achieve the state of forgetting to eat and sleep. Of course, it is not recommended to stay up late. After all, only with energy can we fight a protracted war. But you must be in a state when doing experiments. You can think about the problems to be solved when eating, sleeping and going to the bathroom. In this way, it is difficult for you to succeed.
[

](https://blog.csdn.net/hml666888/article/details/80473792)

Topics: C Linux network