Chapters 6 and 7 Embedded Linux Development
The BIOS reads the first 512 bytes of the hard disk (MBR). Only one OS boot record can be stored in the MBR. If there are multiple systems, there will be problems.
MBR contains part or all of Bootloader and partition table
Bootloader generally contains two stages of code
Bootloader mode:
Self-starting mode: load the OS into RAM from a solid-state storage device such as Flash to run without intervention.
Interactive mode: Download the OS from the Host through the serial port or network interface and run it in RAM, with functions such as programming.
Bootloader function
The first stage:
- Initialize basic hardware
- Automatically move the Bootloader to memory
- Set the stack pointer and zero out the bss segment. Prepare for subsequent code execution.
second stage:
- Initialize the hardware to be used in this stage
- read environment variables
- start up
- Self-starting mode, load the kernel from Flash or through the network and execute
- Interactive download mode, perform related operations after receiving user commands
Embedded Linux development environment
NFS service: file directory sharing
TFTP service: download with TFTP protocol
Minicom software: serial port communication, implement ioctl() method
.config file
CONFIG_RAMDISK = y(yes) m(module) *(default?)
make config
Configure parameters line by line
make menuconfig
Read Kconfig by interface
make xconfig
In the form of a three-dimensional gui
Linux kernel boot
cp arch/arm/boot/zImage /tftpboot
Start the development board and enter the Bootloader
Configure kernel parameters, configure Host/Target ip address
tftp to get the kernel image file
bootz boots the kernel
FrameBuffer Driver
Low-level base console driverdrivers/char/console.c
middle layer driverdrivers/video/fbcon.c fbmem.c
Drivers for top-level specific hardwareradeonfb.c
Device file /dev/fb0
, major device number 29
ioctl() can get screen parameters
mmap maps the screen buffer to user space (passes the mapped space size)
Read and write operation user space address
Linux file system
In embedded Linux applications, the main storage devices are RAM (DRAM, SDRAM) and ROM (often using Flash memory).
Linux file systems are divided into three categories:
- Flash-based file system (entity)
- ARM based file system (memory)
- Network File System NFS
Flash file system
Features of flash memory reading and writing and erasing: the writing operation changes 1 to 0, and the erasing operation restores 0 to 1, and the unit of erasing is block (block)
NOR flash memory: fast reading speed, high price, supports XIP, and reads and writes in words.
NAND flash memory: fast writing speed, high storage density, requires a special system interface, and reads and writes in units of pages.
On top of Flash, MTD (Memory Technology Drivers) device driver support is required. Specific file systems such as jffs2, yaffs, etc. are built on top of MTD, and on top of that is VFS.
The main advantage of using the MTD driver is that it is specially designed for various non-volatile memories (mainly flash memory), so it has better support, management and sector-based erasing and reading of Flash. / Write operation interface.
RAM file system
Ramdisk
Use a part of fixed-size memory as a partition, and put some files that are frequently accessed but will not change (such as a read-only root file system) in memory, which Ramdisk
can significantly improve system performance. In the boot phase of Linux, initrd
a set of mechanisms is provided to load the kernel image and the root file system into memory together.
Network file system
NFS is a technology for sharing files over the network between different machines and different operating systems.
NFS service, pay attention to mount
the command usage method
Linux device drivers
When there is an operating system, the hardware/driver/application relationship
The operating system achieves the purpose of providing convenience for upper-layer applications by making troubles for the driver . When the drivers are all designed according to the device-independent interface provided by the operating system, then the application program will use a unified system call interface to access various devices.
Device driver: As the name implies, it drives the hardware device to work. The device driver directly deals with the underlying hardware, reads and writes device registers according to the specific working mode of the hardware device, completes device polling, interrupt processing, DMA communication, and maps physical memory to virtual memory. , and ultimately enable communication devices to send and receive data, display devices to display text and images, and storage devices to record files and data.
Linux kernel architecture
The Linux kernel is divided into 5 modules:
- Process Scheduler: Responsible for managing CPU resources so that each process can access the CPU as fairly as possible
- Memory Management: Responsible for managing memory resources so that each process can safely share the memory resources of the machine. In addition, memory management will provide a mechanism for virtual memory, which allows a process to use more memory than is available to the system. The unused memory will be stored in the external non-volatile memory through the file system, and then retrieved to the memory when needed. middle.
- Virtual File System (Virtual File System, VFS): The kernel abstracts external devices with different functions into a unified file operation interface
- Network (Network): Responsible for managing the network equipment of the system and implementing various network standards
- Device Control : Almost every system operation is ultimately mapped to a physical device. Except for the processor, memory, and very few other entities, any device control operation in the entirety is controlled by a device specific to the addressable Device-related code to carry out. These codes are called device drivers.
Each piece of code that can be added to the kernel at runtime is called a module. The Linux kernel provides support for many module types, including but not limited to, device drivers. Each module consists of object code (not linked into a complete executable file), can be dynamically linked into the running kernel, through insmod
the program , and through rmmod
the program to link.
Most character devices only provide the ability to access data sequentially, unlike ordinary file access, which can move the access pointer back and forth to access data at different locations.
"crw-rw-r–" the first letter indicates a character device, b stands for block device (block device)
Block devices are generally large-capacity storage devices, and a file system needs to be established for data storage. The block interface must support mounting filesystems.
The smallest unit of a network interface device is a packet .
Build modules
The kernel and user address spaces are isolated, and the kernel can only call its own code.
There is no function in the kernel module main
, there is main
a function in the kernel
Based on kbuild mechanism, write Makefile
KSRC = /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
obj-m = hello.o
default:
$(MAKE) -C $(KSRC) M=$(PWD) modules
clean:
S(MAKE) -C $(KSRC) SUBDIRS=$(PWD) clean
insmod
run, load
rmmod
uninstall
lsmod
View the list of runtime modules
modinfo
View kernel module information
depmod -a
update kernel module index
modprobe
Automatically resolve kernel module dependencies
Classification of devices and modules
Treating devices in the Linux way can be divided into three basic device types: character devices, block devices, and network interfaces . Among them, character devices and block devices /dev
can find corresponding files in the directory.
- Character Device
A character device refers to a device that can only read and write one byte at a time, and cannot randomly read a certain data in the device memory, and reads data in sequence. Character devices are stream-oriented devices . Common character devices include mouse, keyboard, serial port, console, and LED devices. Text console ( /dev/console
) and serial port ( /dev/ttyS0
) are examples of character devices because they represent the stream abstraction well. Character devices are accessed through filesystem nodes , such as /dev/tty1
and /dev/lp0
. A character driver usually implements at least open
the , close
, read
, write
and system calls. The only relevant difference between a character device and an ordinary file is that ordinary files can be moved freely, but most character devices are only data channels, which can only be accessed sequentially . The device file has no file size and is replaced by two numbers: major device number + this device number.
- Block Devices
A block device refers to a device that can read a certain length of data from any location on the device. Block devices include hard disks, disks, U disks, and SD cards. Like character devices, block devices are accessed through filesystem nodes located in /dev
the directory . A block device (such as a disk) should be able to host a file system. **Block and character devices differ only in the way the kernel manages data internally, and therefore in the kernel/driver software interface. **In most Unix-like systems, a block device can only handle several whole blocks whose size is an integer multiple of 2. But in the Linux system, applications are allowed to read and write a block device like a character device, which allows any number of bytes to be transferred at a time.
- Network Interface
Any network transaction is carried out through an interface, that is to say, a network interface is a device capable of exchanging data with other hosts. A network interface can be a hardware device or a pure software device such as a loopback interface. A network interface is responsible for sending and receiving datagrams. Although a network like TCP is stream-oriented, network devices are often designed to handle the sending and receiving of packets, that is to say, they know nothing about individual connections, they only process packets, so the network interface is not a flow- oriented For streaming devices , a network interface is not /dev/tty1
as easily mapped to a node in the file system. The way Unix provides access to interfaces is still by assigning them a name (for example eth0
), but this name has no corresponding entry in the file system, network interfaces appear in the form of configuration files .
- You can call
dmesg
"Hello, world" to see the printout, or you can usecat /proc/devices
The difference between kernel modules and applications
- Applications are executed sequentially from start to finish, while functions in kernel modules are called passively.
- ** Kernel modules cannot call C libraries. **Because kernel modules are only linked to the kernel, the only functions a module can call are those exported by the kernel.
- Kernel modules have to do some cleanup. When the application exits, it is not necessary to release the resources it applied for before it is called, but when the module exits, it must confirm all the resources it applied for when it was loaded, otherwise, the resources it applied for will remain in the system until the system is restarted. middle.
- **Applications run in user space, while kernel modules run in kernel space. **Under Unix, the kernel runs at the highest level, at which everything is allowed, and applications run at the lowest level , where the processor controls direct access to hardware and illegal access to memory.
The process of compiling and loading modules
obj-m
The module to be declared is defined, that is, the .o file, the operating system kernel compilation directoryKSRC
is defined , and the location of the source file is specified . The Linux kernel compilation uses the Kbuild system. This Makefile will be called twice. The first time is to construct the kernel directory tree, and the second time is to call after the kernel source tree is found to run and call the kernel to build the actual module building work . After success, the file will be generated under the current path .M
default
$(MAKE)
make
.ko
The following two header files are required for loadable modules
#include <linux/module.h>
#include <linux/init.h>
The former contains definitions of functions and symbols required by a large number of loaded modules. The latter specifies initialization and cleanup functions. Note that at the end of the module we use MODULE_AUTHOR("Sheng LYU")
two MODULE_LICENSE("GPL")
macros, the former defines the author and the latter defines the code license.
Generally speaking, the main number is used to identify the driver connected to the device, such as /dev/null
and /dev/zero
are managed by driver 1, while the virtual console and serial port terminal are both managed by driver 4. The minor number is used by the kernel to decide which device to refer to.
- Device numbers can be created or removed using:
sudo mknod /dev/test001 c 240 0
rm /dev/test001
important file structure
Most basic drivers include three important kernel data structures called file_operations
, file
, and inode
. file_operation
The structure description structure is a character driver to establish the connection between the device and the number. The scull device driver implements only the most important device methods. Its file_operations structure is initialized as follows:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
/* loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).*/
.read = scull_read,
/*ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).*/
.write = scull_write,
/*ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.*/
.ioctl = scull_ioctl,
/*int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.*/
.open = scull_open,
/*int (*open) (struct inode *, struct file *);
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.*/
.release = scull_release,
/*int (*release) (struct inode *, struct file *);
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.*/
};
Registration and removal of character devices
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
/*major 是需要使用的主编号, name 是驱动的名子(出现在 /proc/devices), fops 是缺省的 file_operations 结构. 一个对 register_chrdev 的调用为给定的主编号注册 0 - 255 的次编号, 并且为每一个建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.*/
int unregister_chrdev(unsigned int major, const char *name);
/*major 和 name 必须和传递给 register_chrdev 的相同, 否则调用会失败.*/
open and release methods
open
method provided to the driver to do any initialization in preparation for subsequent operations. In most drivers, open should do the following:
- Check for device-specific errors (e.g. device not ready, or similar hardware errors)
- If it is turned on for the first time, initialize the device
- Update the f_op pointer if necessary.
- Allocate and populate any data structures to be put
filp->private_data
into
Function prototype:
int (*open)(struct inode *inode, struct file *filp);
release
The role of methods is the opposite open
of . Sometimes you'll find implementations of methods called device_close
instead of device_release
. Anyway, device methods should perform the following tasks:
open
freesfilp->private_data
anything allocated in- At the end
close
close the device
read and write methods
Both the read and write methods perform similar tasks, that is, copying data from and to application code. Therefore, their prototypes are quite similar, and they can be introduced together:
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
For the two methods, filp
it is a file pointer, count
which is the size of the requested transfer data. buff
The parameter points to the buffer holding the written data, or the empty buffer to put the new data. Finally, offp is a pointer to a "long offset type" object , which indicates the file location the user is accessing.
Note that the buff parameter of read
the and write
method is a user-space pointer . Therefore, it cannot be directly dereferenced by kernel code.
Obviously, your driver must be able to access user-space caches to do its job. However, for security reasons this access must use special, kernel-provided functions. We introduce a few such functions (defined in ), and the <asm/uaccess.h>
rest in the "Using ioctl Parameters" section of Chapter 1. They use some special, architecture-dependent tricks to ensure safe and correct data transfers between the kernel and userspace.
The read and write code in scull needs to copy entire segments of data to and from user address space. This capability is provided by the following kernel functions, which copy an arbitrary byte array, and lie at the heart of most read and write implementations.
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
//将数据从内核拷贝至用户空间,用于read操作
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
//将数据从用户空间拷贝到内核中,用于write操作
ioctl interface
Most drivers require - in addition to the ability to read and write to the device - the ability to perform various hardware controls through the device driver. Most devices can perform operations beyond simple data transfers; userspace must often be able to request, for example, that the device be locked Its door, eject its media, report an error message, change the baud rate, or self-destruct. These operations are often supported by the ioctl method, which is implemented through the system call of the same name.
int ioctl(int fd, unsigned long cmd, ...); //应用层
long (*unlocked_ioctl)(struct file* file,
unsigned int cmd,
unsigned long arg);//内核层
exercise
- mknod cannot create ordinary files . c character file, p pipe file
- The function
copy_to_user
usually appears in the read method function of the device driver (the read here can be regarded as reading the kernel, and copy the content from the kernel to the user space after reading the kernel) - The role of GNU make in the software development process is to reduce the compilation and linking process according to the dependency relationship in the program compilation stage, and improve the compilation efficiency
- When a process starts, three files are opened by default: standard input, standard output, and standard error handling (stdin, stdout, stderr)
- Inode nodes and files are not in one-to-one correspondence, the inode numbers of soft links are different, and the inodes of hard links are the same.
- The device driver is the interface between the upper application and the underlying hardware, providing a mechanism for accessing the hardware.
- The upper-layer application accesses the device through the device node file under the file system/dev, and triggers the corresponding device driver in the kernel to work
- The Linux kernel finds the corresponding device driver module through the major device number of the device
- The device driver module distinguishes subdivided devices in a large class of devices through the minor device number
struct file_operations device_fops = {
llseek: device_llseek,
read: device_read,
write: device_write,
unlocked_ioctl: device_ioctl,
open: device_open,
release: device_release
};
or
struct file_operations device_fops = {
.owner = THIS_MODULE,
.llseek = device_llseek,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.open = device_open,
.release = device_release,
};
- The llseek() method is the specific implementation of the lseek() system call, which is used to complete the positioning operation of the device
- The ioctl() method, in addition to reading and writing the device, the device driver also needs to provide a variety of hardware control capabilities, such as configuring the device, entering or exiting a certain operating mode, generally not through read/write and other file operations. , such as the setting of the serial communication protocol (baud rate, data format, etc.). In the Linux kernel, the unlocked_ioctl() method is used to implement the ioctl() system call.
- int ioctl(int fd, int cmd, ...) fd device file number, cmd command, the third parameter can point to various data types.
- Large-capacity data transmission must use
read, write
the function - If a process calls
read
, but has no data to read, the process must block. As soon as the data arrives, the process wakes up and returns the data to the caller. - If the data is not ready, simply return -EAGAIN, so non-blocking operations return immediately.
- The device driver provides the mechanism (mechanism), and the application provides the policy (strategy)
- /proc is a special pseudo-file system that the kernel uses to output information to the outside world. Each file under /proc is bound to a kernel function that dynamically generates the "content" of the file when the file is read. In the ux kernel, the unlocked_ioctl() method is used to implement the ioctl() system call
. - int ioctl(int fd, int cmd, ...) fd device file number, cmd command, the third parameter can point to various data types.
- Large-capacity data transmission must use
read, write
the function - If a process calls
read
, but has no data to read, the process must block. As soon as the data arrives, the process wakes up and returns the data to the caller. - If the data is not ready, simply return -EAGAIN, so non-blocking operations return immediately.
- The device driver provides the mechanism (mechanism), and the application provides the policy (strategy)
- /proc is a special pseudo-file system that the kernel uses to output information to the outside world. Each file under /proc is bound to a kernel function that dynamically generates the "content" of the file when the file is read
- Incorrect use of pointers in user space usually leads to 'Segment Fault', illegal use of memory addresses in kernel space usually leads to "oops"