With more and more connected devices around us, the chance that you've been hit by an update notification is high. But what do these software updates do? How do they actually work, and why are they important? Hardware and SoftwareModern electronic devices require two main parts to function: the hardware and the software. The hardware usually refers to physical electronic pieces inside a device (usually a collection of microchips, logic gates and specialised processing chips, such as those to process radio waves for communication, or process audio for sound), while the software is the set of instructions that tells the device what to do. Hardware without software doesn't do anything (a computer without an Operating System such as Windows or MacOS isn't capable of running anything) and software without hardware have nothing to send instructions to (a copy of windows or MacOS is useless without a computer to run it on).
Types of SoftwareFirmwareDue to the complexities of modern electronic devices, an abstraction layer is often created between the physical chips and the software (usually some form of operating system, such as Android or Windows). This abstraction layer is referred to as firmware. Firmware allows the operating system to make generic calls to the hardware (for example, open microphone, to allow for an audio recording or phone call), which the firmware interprets and then sends the correct instructions to the hardware, the physical chips, to turn on the microphone. Firmware exists because there is hundreds of different hardware modules (such as bluetooth, wifi, cameras etc.) produced by different manufacturers that all behave slightly differently. It would be very challenging for an operating system to be able to communicate directly with such a disperate array of hardware, so instead they communicate with the firmware which exposes a known method or action that the operating system understands.
Operating SystemAn operating system is a core programme that manages the interactions between other programmes and the hardware. It is usually comprised of a core (also known as kernel) which enumerates the available hardware, either directly for simple devices or via firmware for more complex devices. tprovides a scheduler which tries to balance the contention of multiple tasks (applications) being run simultaneously around the ability of the processor (the brain of the device) usually only being able to run one task at a time. For example, the Operating System will be in charge of making sure that launching an app such as the web browser won't interupt sound being played by another application. Modern operating systems like Windows, Android, iOS, or Linux also usually bundle a number of ancillary services, such as the user interface and basic utilities for the device. This includes, for example, a sound managing interface to set the volume of applications playing on the device or a network interface to easily connect to WiFi networks.
A software update (also known as patch) is a set of changes to a software to update, fix or improve it. Changes to the software will usually either fix bugs, fix security vulnerabilities, provide new features or improve performances and usability. Infrequently, patches may also be used to limit functionality, remove or disable features. Depending on the software, updates can either be installed manually or automatically if the device is connected to the internet and has the appropriate capabilities (for instance, an Android phone that updates its software on its own). Software updates are particularly important when applied to the Operating System given the reliance of other software (such as apps or drivers) on it. For example, a major release of an Operating System such as Android or iOS might render a number of apps obsolete, if all version released after the update aren't compatible with the previous version of the OS. This could prevent people from accessing important services as illustrated with some covid-19 track and track apps which were only compatible with specific versions of iOS and Android. From a security standpoint, software updates have important implications. When an update includes a fix for security vulnerabilities, any device running an out-of-date version of the software is particularly vulnerable. This allows malicious actors to know what vulnerabilities exist on a given system and, consequently, puts devices running this software (version) more at risk. For example, using an outdated version of Android (such as version 4) means that all the security vulnerabilities spotted and fixed in following versions still exist on any device that uses the older version 4. Lack of software update might also have a negative impact on a device's functionalities for example by making some its function obsolete (e.g.: a browser that do not support the latest security protocols and therefor can't display websites properly). It might also mean that identified bugs and problem might never be fixed (e.g.: poor battery). Current market practices don't impose minimum software support for a device or software version on release, meaning a device can be produced, released and sold while embeding an outdated Operating System or without offering regular software updates. This fundamentally allow manufacturers to sell devices that might become outdated and vulnerable within a couple of month of their release. This is a practice that's reguylarly observed which puts users' security and privacy at a very high risk.
AudienceDevelopersContentHow-Tos
In the article ‘An Introduction to the Linux Kernel’ in the August 2014 issue of OSFY, we wrote and compiled a kernel module. In the second article in this series, we move on to device drivers. Have you ever wondered how a computer plays audio or shows video? The answer is: by using device drivers. A few years ago we would always install audio or video drivers after installing MS Windows XP. Only then we were able to listen the audio. Let us explore device drivers in this column. A device driver (often referred to as ‘driver’) is a piece of software that controls a particular type of device which is connected to the computer system. It provides a software interface to the hardware device, and enables access to the operating system and other applications. There are various types of drivers present in GNU/Linux such as Character, Block, Network and USB drivers. In this column, we will explore only character drivers. Character drivers are the most common drivers. They provide unbuffered, direct access to hardware devices. One can think of character drivers as a long sequence of bytes — same as regular files but can be accessed only in sequential order. Character drivers support at least the open(), close(), read() and write() operations. The text console, i.e., /dev/console, serial consoles /dev/stty*, and audio/video drivers fall under this category. To make a device usable there must be a driver present for it. So let us understand how an application accesses data from a device with the help of a driver. We will discuss the following four major entities.
Let us take an example where a user-space application sends data to a character device. Instead of using an actual device we are going to use a pseudo device. As the name suggests, this device is not a physical device. In GNU/Linux /dev/null is the most commonly used pseudo device. This device accepts any kind of data (i.e., input) and simply discards it. And it doesn’t produce any output. In the above example, echo is a user-space application and null is a special file present in the /dev directory. There is a null driver present in the kernel to control the pseudo device. In the above output there are two numbers separated by a comma (1 and 3). Here, ‘1’ is the major and ‘3’ is the minor number. The major number identifies the driver associated with the device, i.e., which driver is to be used. The minor number is used by the kernel to determine exactly which device is being referred to. For instance, a hard disk may have three partitions. Each partition will have a separate minor number but only one major number, because the same storage driver is used for all the partitions. The kernel uses the dev_t type to store major and minor numbers. dev_t type is defined in the <linux/types.h> header file. Given below is the representation of dev_t type from the header file: #ifndef _LINUX_TYPES_H #define _LINUX_TYPES_H #define __EXPORTED_HEADERS__ #include <uapi/linux/types.h> typedef __u32 __kernel_dev_t; typedef __kernel_dev_t dev_t;dev_t is an unsigned 32-bit integer, where 12 bits are used to store the major number and the remaining 20 bits are used to store the minor number. But don’t try to extract the major and minor numbers directly. Instead, the kernel provides MAJOR and MINOR macros that can be used to extract the major and minor numbers. The definition of the MAJOR and MINOR macros from the <linux/kdev_t.h> header file is given below: #ifndef _LINUX_KDEV_T_H #define _LINUX_KDEV_T_H #include <uapi/linux/kdev_t.h> #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))If you have major and minor numbers and you want to convert them to the dev_t type, the MKDEV macro will do the needful. The definition of the MKDEV macro from the <linux/kdev_t.h> header file is given below: #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))We now know what major and minor numbers are and the role they play. Let us see how we can allocate major numbers. Here is the prototype of the register_chrdev(): int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);This function registers a major number for character devices. Arguments of this function are self-explanatory. The major argument implies the major number of interest, name is the name of the driver and appears in the /proc/devices area and, finally, fops is the pointer to the file_operations structure. The values of the major and name parameters must be the same as those passed to the register_chrdev() function; otherwise, the call will fail. Without discussing lengthy theory, let us write our first ‘null’ driver, which mimics the functionality of a /dev/null pseudo device. Given below is the complete working code for the ‘null’ driver. Open a file using your favourite text editor and save the code given below as null_driver.c: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/kdev_t.h> static int major; static char *name = "null_driver"; static int null_open(struct inode *i, struct file *f) { printk(KERN_INFO "Calling: %s\n", __func__); return 0; } static int null_release(struct inode *i, struct file *f) { printk(KERN_INFO "Calling: %s\n", __func__); return 0; } static ssize_t null_read(struct file *f, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Calling: %s\n", __func__); return 0; } static ssize_t null_write(struct file *f, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Calling: %s\n", __func__); return len; } static struct file_operations null_ops = { .owner = THIS_MODULE, .open = null_open, .release = null_release, .read = null_read, .write = null_write }; static int __init null_init(void) { major = register_chrdev(0, name, &null_ops); if (major < 0) { printk(KERN_INFO "Failed to register driver."); return -1; } printk(KERN_INFO "Device registered successfully.\n"); return 0; } static void __exit null_exit(void) { unregister_chrdev(major, name); printk(KERN_INFO "Device unregistered successfully.\n"); } module_init(null_init); module_exit(null_exit); MODULE_AUTHOR("Narendra Kangralkar."); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Null driver"); Our driver code is ready. Let us compile and insert the module. In the article last month, we did learn how to write Makefile for kernel modules. [mickey]$ make [root]# insmod ./null_driver.koWe are now going to create a device file for our driver. But for this we need a major number, and we know that our driver’s register_chrdev() function will allocate the major number dynamically. Let us find out this dynamically allocated major number from /proc/devices, which shows the currently loaded kernel modules: [root]# grep "null_driver" /proc/devices 248 null_driverFrom the above output, we are going to use ‘248’ as a major number for our driver. We are only interested in the major number, and the minor number can be anything within a valid range. I’ll use ‘0’ as the minor number. To create the character device file, use the mknod utility. Please note that to create the device file you must have superuser privileges: [root]# mknod /dev/null_driver c 248 0Now it’s time for the action. Let us send some data to the pseudo device using the echo command and check the output of the dmesg command: [root]# echo "Hello" > /dev/null_driver [root]# dmesg Device registered successfully. Calling: null_open Calling: null_write Calling: null_releaseYes! We got the expected output. When open, write, close operations are performed on a device file, the appropriate functions from our driver’s code get called. Let us perform the read operation and check the output of the dmesg command: [root]# cat /dev/null_driver [root]# dmesg Calling: null_open Calling: null_read Calling: null_releaseTo make things simple I have used printk() statements in every function. If we remove these statements, then /dev/null_driver will behave exactly the same as the /dev/null pseudo device. Our code is working as expected. Let us understand the details of our character driver. The prototype of the open() and release() functions is exactly same. These functions accept two parameters—the first is the pointer to the inode structure. All file-related information such as size, owner, access permissions of the file, file creation timestamps, number of hard-links, etc, is represented by the inode structure. And each open file is represented internally by the file structure. The open() function is responsible for opening the device and allocation of required resources. The release() function does exactly the reverse job, which closes the device and deallocates the resources. Implementation of the full pseudo driver If you want to learn more about GNU/Linux device drivers, the Linux kernel’s source code is the best place to do so. You can browse the kernel’s source code from http://lxr.free-electrons.com/. You can also download the latest source code from https://www.kernel.org/. Additionally, there are a few good books available in the market like ‘Linux Kernel Development’ (3rd Edition) by Robert Love, and ‘Linux Device Drivers’ (3rd Edition) which is a free book. You can download it from http://lwn.net/Kernel/LDD3/. These books also explain kernel debugging tools and techniques. |