Later on we will get to linked lists, queues and trees. But first I want to stop and talk about something smaller. The struct.
Most beginners meet the struct in C, write one or two small examples, and move on. It feels like a minor feature.
But this small feature is doing a huge amount of work in the real world. The Linux kernel is written in C, and it is built almost entirely out of structs. And Linux runs on a lot of machines.
So in this post I will first explain what a struct is, as simply as I can. Then I will show how the Linux kernel uses that same idea to keep real machines running.
Where does the Linux kernel run?
I want to keep the claims here honest, so I will only use numbers that are easy to check.
Android phones run on the Linux kernel. Google has said there are more than three billion active Android devices. That covers phones, tablets, TVs and cars.
Every one of the TOP500 supercomputers has run a Linux based operating system since November 2017. That is still true on the June 2026 list.
On top of that, Linux is very common on web servers, cloud machines, home routers and all kinds of small embedded boards. I will not throw a precise number at you for those, because the counts vary by survey. But "a lot" is a fair word.
Now here is the part I find interesting. Inside all of these machines, the kernel keeps track of what is going on using structs. Plain C structs. The same kind you can write in your first week of learning C.
What is a C struct?
A struct lets you put related pieces of data together under one name.
Say you want to store a student's roll number and marks along with the name. You could keep separate variables, but that gets messy fast once you have 50 students. A struct fixes this.
/* A tiny beginner example */ struct student { int roll_no; char name[50]; float marks; }; struct student s1 = { 1, "Asha", 87.5 }; printf("%s scored %.1f\n", s1.name, s1.marks); That is it. struct student is now a new type, and each variable of that type carries its own copy of the fields. You reach a field with a dot, like s1.name. If you have a pointer to it, you use an arrow, like p->name.
A struct can also hold other structs, or pointers to them. That is where things get interesting, and it is exactly what the Linux kernel does all day.
task_struct: one kernel struct for every running program
When you open an app on your phone, the kernel creates a new process for it. To keep track of that process, it fills in one struct. In the Linux kernel this is called task_struct, also known as the process descriptor.
Every running program on a Linux machine has one. Your phone right now likely has hundreds of them in memory.
Here is a very small slice of it. This is simplified. The real struct has a lot more fields.
/* Simplified. Only a few of the real fields are shown. */ struct task_struct { volatile long state; /* running, sleeping, stopped... */ struct mm_struct *mm; /* this process's memory */ struct task_struct *parent; /* who started it */ struct list_head children; /* its child processes */ struct list_head sibling; /* its brothers and sisters */ struct list_head tasks; /* link in the list of all processes */ struct files_struct *files; /* its open files */ }; Look at it next to the student struct. It is the same pattern. The difference is that many of these fields are pointers to other structs. The mm field points to a struct about memory. The files field points to a struct about open files. One struct leads to another, and together they describe one running program.
Some fields are not pointers at all. A field like children is a whole small struct sitting inside the big one. So a struct can hold plain values and pointers, and it can also hold whole structs. That freedom is what lets one type describe something as messy as a running program.
Kernel structs that act like objects
C does not have classes. But the kernel still gets something very close to objects, using structs full of function pointers.
When a program opens a file, the kernel creates a file struct for it. One of its fields is f_op, which points to a file_operations struct. That second struct is basically a table of functions.
/* Simplified. The real table has many more entries. */ struct file_operations { ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*open) (struct inode *, struct file *); int (*release)(struct inode *, struct file *); }; struct file { struct file_operations *f_op; /* what this file can do */ loff_t f_pos; /* where we are in the file */ /* ...many more fields... */ }; Here is why this matters. A file on a disk, a pipe, and a device all need to be read in very different ways. But the kernel does not care. It just calls file->f_op->read(...). Each kind of file fills the table with its own functions. So the same line of code does the right thing for each one.
If you know a bit of Java or C++, this is the same idea as a method, just built by hand.
One more small idea: the kernel's thread_union
The kernel uses a union called thread_union. A union is like a struct, except all its fields share the same memory. In thread_union, a small struct called thread_info and the kernel stack for a process live in the same block of memory. On older 32-bit x86 systems that block was 8 KB. It saves space, and it lets the kernel find thread_info quickly from the stack.
A struct is a design tool in the Linux kernel
Put all of this together and a pattern shows up. The kernel decides what a thing is, writes one struct for it, and then makes one copy of that struct for every real thing in the machine. One task_struct per program. One file per open file. A device or a mounted disk gets its own struct too.
That is a way of thinking, not just a C feature. When you learn to design a good struct, you are learning to model the real world in code.
Why spend a whole series on it?
Because almost everything else in C is built on top of it. Every data structure you will meet later, from a simple list to a tree, is a struct with some extra pointers. If structs feel shaky, those topics will feel shaky too.
And this is not only for classrooms. It is running inside your phone as you read this, and inside the server that sent you this page. The kernel developers did not need some fancy new tool. They used the plain C struct, very carefully, for decades.
So I want to slow down here and get comfortable with structs first. Make sure you know when to use the dot and when to use the arrow. Know what it means for a struct to hold a pointer to another struct. Once that feels easy, a lot of C starts to make sense.
What comes next
In the next few posts we will stay with the struct itself. We will look at how a struct sits in memory, why sizeof sometimes gives a bigger number than you expect, and how structs go inside other structs. After that come unions, bitfields and function pointers inside structs, all with real examples from the Linux kernel.
Once that ground is solid, we will start a separate series on linked lists, queues and trees.
If you want to go through all of this step by step with exercises, our C and Data Structures course at Jupius Learning covers it.
Originally published on Jupius Learning.

