Talking about the idea of object-oriented design and its embodiment in the Linux kernel

Object-oriented programming (OOP) is a design idea or architectural style . The father of OO languages, Alan Kay, inventor of Smalltalk, had this to say about OOP:

OOP should embody a network structure, each node "Object" on this structure can only communicate with other nodes through "message". Each node will have an internal hidden state, and the state cannot be modified directly, but should be modified indirectly through message passing.

With this passage as the opening, start the discussion of this article.

1. The essence of object-oriented

1.1 What problem does object-oriented programming solve?

Most programmers have learned c language, especially embedded engineers, may only know c language.

C language is a typical process-oriented language, everything is a process. A simple single-chip program may only have a few lines, and many are no more than a few hundred lines. At this time, a person's ability can completely walk through the entire code, but after the operating system is introduced, things become complicated.

Various functions such as process scheduling and memory management have increased the amount of code dramatically. A simple RTOS real-time operating system has reached tens of thousands of lines of code. At this time, it is not impossible to get through, but it is more difficult.

However, the amount of Linux code is not something that normal people can read. Reading one line of code in one second and reading 12 hours a day will take several years to read the source code of Linux. This is just reading, not understanding.

Another simple example

Small companies often only have a few people, everyone works together, and you can see exactly what everyone is doing.

What about in big companies? In a company with thousands or tens of thousands of people, it is completely impossible for you to figure out what everyone is doing. So there are departments, each department distinguishes responsibilities and performs its own duties. You may not know the details of a single person's work, but you only need to know that the sales department is responsible for sales, the R&D department is responsible for research and development, and the production department is responsible for production... ..

The fundamental problem to be solved by object-oriented programming is to organize the program systematically, so as to facilitate the construction of large and complex programs.

1.2 The relationship between programming languages ​​and object-oriented programming

Many people tend to associate programming languages ​​with object-oriented, for example, c language is a process-oriented language, and c++ is an object-oriented language, which is not entirely accurate.

Object-oriented is a kind of design idea, and object-oriented can also be fully realized in C language, and programs written in C++ and other languages ​​may also be process-oriented rather than object-oriented .

One of the most obvious examples of object-oriented implementation in C language is the Linux kernel, which can be said to completely adopt the object-oriented design idea, which fully embodies multiple relatively independent components (process scheduling, memory management, file system...) ideas of collaboration. Although the Linux kernel is written in C language, it is more OOP than many programs written in so-called OOP languages.

Here I use c++ to explain why object-oriented languages ​​​​also write process-oriented programs:

This paragraph is suitable for friends with some C++ foundation, if you don’t know C++, you can skip it.

For example, a program to calculate the total price is nothing more than the number * unit price

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	double result(void){
		return price*num;
	}	
};
int main ()
{
	calculate a;
	a.price=1;
	a.num=2;
	cout<<a.result()<<endl;
	return 0;
}

Add a function, Double 11, 20% off, how would you write it?

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	int date;
	double result(void){
		if(date==11)
			return price*num*0.8;
		else
			return price*num;
	}	
};
int main ()
{
	calculate a;
	a.price=1;
	a.num=2;
	cout<<"please input the date:"<<endl;
	cin>>a.date;
	cout<<a.result()<<endl;
	return 0;
}

If it is written like this, it is a typical process-oriented thinking, why? If there is another 30% discount on Double 12, how to write it according to this idea? Then add an if else to the calculate class to judge, what if there is a 50% discount for the Chinese New Year? Then add an if else in the calculate class to judge. Let's take a look at how to write object-oriented thinking:

#include<iostream>
using namespace std;
class calculate{
public:
	double price;
	double num;
	virtual double result(void){
	}	
};
class normal:public calculate{
public:
	double result(void){
		return price*num;
	}	
};
class discount:public calculate{
public:
	double result(void){
	return price*num*0.8;
	}
};
int main ()
{
	calculate *a;
	int date;
	cout<<"please input the date:"<<endl;
	cin>>date; 
	if (date==11){
		a = new discount;
	} else {
		a = new normal;
	}	
	a->num=1;
	a->price=2;
	cout<<a->result()<<endl;
	return 0;
}

Using inheritance and polymorphism, double 11 is abstracted into a separate class, inherited from the calculate class, and normal is also abstracted into a separate class, inherited from the calculate class. Provide the implementation of result in subclasses.

If there is a Double 12, how should I write it? Write another double 12 class, inherit from the calculate class and implement your own result calculation.

Some friends may be wondering, don’t you still need to make an if else judgment in the main function main? What’s the difference from the first one?

The difference is that I no longer need to modify the original code when I add new requirements, (the original code refers to the core part of the calculation) fully absorbs the characteristics of the original code.

When I don't need a certain function, I just delete the corresponding class, which is flexible and scalable.

It seems that there is no difference here because the code is simple. When implementing a complex function, after the code has been tested, it should not be touched. The first method continuously modifies the core part, which brings great hidden dangers, and if The original code is complex and difficult to modify.

It is important to emphasize here that if you simply write code in an object-oriented programming language, the program will not automatically become object-oriented, and you may not be able to obtain various benefits of object-oriented programming.

So object-oriented focuses on thinking , not programming language. In the second section, I will talk about how the linux kernel embodies object-oriented thinking in c language.

1.3 Does object-oriented refer to encapsulation, inheritance, and polymorphism?

In fact, from the example of a large company in 1.1 and the example of two different c++ programs in 1.2, it can be seen that encapsulation, inheritance, and polymorphism are only the characteristics of object-oriented programming, not the core idea .

The most fundamental point of object-oriented programming is to shield and hide

Each department is relatively independent and has its own charter, work methods and rules. Independence means "hiding internal state". For example, you can only say to apply for a certain department to do something according to the charter, but you can't tell whoever in the department to do it by when. You can't see these internal details, and you can't control them.

One thing that should always be kept in mind, object orientation is for the convenience of building large programs with high complexity.

2. The embodiment of object-oriented thinking in the Linux kernel

2.1 Package

The definition of encapsulation is to hide the properties and implementation details of the object in the program, only expose the interface to the outside world, and control the access level of reading and modifying properties in the program; combine the abstracted data and behavior (or function) to form an organic The whole, that is, organically combine the data and the source code for operating the data to form a "class", in which both data and functions are members of the class.

Object-oriented encapsulation puts data and methods (functions) together.

In C language, define a variable, int a, and then define many functions fun1, fun2, fun3.

Through pointers, these functions can modify a, and even these functions are not necessarily in the same .c file as a, which is particularly confusing.

But we can also do encapsulation:

struct file {
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	spinlock_t		f_lock;
	enum rw_hint		f_write_hint;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
    略去一部分
}

For example, the struct file in the Linux kernel contains various attributes of the file, and also contains the file_operrations structure, which is a bunch of operation functions for the file

struct file_operations {
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	int (*open) (struct inode *, struct file *);
    略去一部分
} 

The file_operations structure is a bunch of function pointers, not real operation functions, this is to achieve polymorphism.

In fact, this example is also very similar to inheritance, struct file inherits everything from struct file_operations, but I will use other examples to better reflect inheritance.

2.2 Inheritance

Objects of special classes (or subclasses, derived classes) have all the attributes and services of their general classes (or parent classes, base classes), which is called the inheritance of special classes from general classes.

From the point of view of the idea and purpose of inheritance, it is to enable subclasses to share the data and methods of the parent class, and at the same time define and expand new data members and methods on the basis of the parent class, thereby eliminating the repeated definition of classes and improving the software. reusability.

A linked list structure in C language is as follows:

struct A_LIST {
    data_t        data; // 不同的链表这里的data_t类型不同。
    struct A_LIST    *next;
};

There is a general linked list structure in the Linux kernel:

struct list_head {
    struct list_head *next, prev;
);

This structure can be regarded as a base class, and its basic operations are insertion and deletion of linked list nodes, initialization and movement of linked lists, etc. If other data structures (which can be regarded as subclasses) are to be organized into a doubly linked list, this general linked list object can be included in the linked list node (which can be regarded as inheritance).

As in the example above, we only need to declare

struct A_LIST {
    data_t            data;
    struct list_head    *list;
};

The essence of a linked list is a linear sequence, and its basic operations are insertion and deletion, etc. The difference between different linked lists lies in the data type stored in each node, so the characteristics of the linked list are abstracted into this general linked list, which exists as a parent class. The linked list inherits the basic methods of this parent class and expands its own properties.

As a connector, the general linked list is only responsible for its own structure, and does not need to pay attention to the structure that really belongs. As with inheritance, methods of the parent class cannot and do not need to operate on members of the subclass.

Regarding the host pointer acquisition method of the linked list structure,

Get the offset of the member MEMBER in the structure type TYPE within the structure

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

Obtain the pointer of the member structure type through the pointer ptr pointing to the member member

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
         (type *)((char *)__mptr - offsetof(type, member)); })

2.3 Polymorphism 

The most obvious example of polymorphism in Linux is the interaction between the character device application program and the driver program. The application program calls open, read, write and other functions to open the device to operate, and does not care how open, read, and write are implemented. The implementation of these functions is in the driver program, and the open, read, and write functions of different devices are different, realizing the same function as the runtime polymorphism in polymorphism.

Process simplification is actually different driver implementation

struct file_operations drv_opr1

struct file_operations drv_opr2

struct file_operations drv_opr3

When the application is running, find the corresponding struct file_operations according to the device number, and point the pointer to it, and then call the open, read, and write functions in the corresponding struct file_operations (the actual process is more complicated than this).

A c program with an object-oriented prototype:

#include<stdio.h>
double normal_result(double price,double num)
{
	return price * num; 
}
double discount_result(double price,double num)
{
	return price * num * 0.8; 
}

struct calculate{
	double price;
	double num;
	double (*result)(double price,double num);
};

int main ()
{
	struct calculate a;
	int date;
	a.price=1;
	a.num=2;
	printf("please input the date:\n");
	scanf("%d",&date);
	if(date==11)
		a.result=discount_result;
	else
		a.result=normal_result;
	printf("%lf\n",a.result(a.price,a.num));
	return 0;
}

Guess you like

Origin blog.csdn.net/freestep96/article/details/127356581