Linux标准IO-系统调用详解

1.1 系统调用

系统调用(system call)其实是 Linux 内核提供给应用层的应用编程接口(API),是 Linux 应用层进入内核的入口。不止 Linux 系统,所有的操作系统都会向应用层提供系统调用,应用程序通过系统调用来使用操作系统提供的各种服务。

通过系统调用,Linux 应用程序可以请求内核以自己的名义执行某些事情,譬如打开磁盘中的文件、读写文件、关闭文件以及控制其它硬件外设。

通过系统调用 API,应用层可以实现与内核的交互,其关系可通过下图简单描述:

图 1.1.1 内核、系统调用与应用程序

内核提供了一系列的服务、资源、支持一系列功能,应用程序通过调用系统调用 API 函数来使用内核提供的服务、资源以及各种各样的功能,如果大家接触过其它操作系统编程,想必对此并不陌生,譬如Windows 应用编程,操作系统内核一般都会向应用程序提供应用编程接口 API,否则我们将我无法使用操作系统。

应用编程与裸机编程、驱动编程有什么区别?

在学习应用编程之前,相信大家都有过软件开发经验,譬如 51、STM32 等单片机软件开发、以及嵌入式 Linux 硬件平台下的驱动开发等,51、STM32 这类单片机的软件开发通常是裸机程序开发,并不会涉及到操作系统的概念,那应用编程与裸机编程以及驱动开发有什么区别呢?

就拿嵌入式 Linux 硬件平台下的软件开发来说,我们大可将编程分为三种,分别为

  • 裸机编程
  • Linux 应用编程
  • Linux 驱动编程

首先对于裸机编程这个概念来说很好理解,一般把没有操作系统支持的编程环境称为裸机编程环境,譬如单片机上的编程开发,编写直接在硬件上运行的程序,没有操作系统支持;狭义上 Linux 驱动编程指的是基于内核驱动框架开发驱动程序,驱动开发工程师通过调用 Linux 内核提供的接口完成设备驱动的注册,驱动程序负责底层硬件操作相关逻辑,如果学习过 Linux 驱动开发的读者,想必对此并不陌生;而 Linux 应用编程(系统编程)则指的是基于 Linux 操作系统的应用编程,在应用程序中通过调用系统调用 API 完成应用程序的功能和逻辑,应用程序运行于操作系统之上。

通常在操作系统下有两种不同的状态:

内核态和用户态,应用程序运行在用户态、而内核则运行在内核态。

关于应用编程这个概念,以上给大家解释得很清楚了,以实现点亮一个 LED 功能为例,给大家简单地说明三者之间的区别,LED 裸机程序如下所示:

复制代码
static void led_on(void) {
	/* 点亮 LED 硬件操作代码 */
}

static void led_off(void) {
 /* 熄灭 LED 硬件操作代码 */
}

int main(void) {
	 /* 用户逻辑 */
	 for ( ; ; ) {
		 led_on(); //点亮 LED
		 delay(); //延时
		 led_off(); //熄灭 LED
		 delay(); //延时
	 } 
}

可以看到在裸机程序当中,LED 硬件操作代码与用户逻辑代码全部都是在同一个源文件(同一个工程)中实现的,硬件操作代码与用户逻辑代码没有隔离,没有操作系统支持,代码编译之后直接在硬件平台运行,俗称"裸跑"。我们再来看一个 Linux 系统下的 LED 驱动程序示例代码,如下所示:

复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

static void led_on(void) {
 /* 点亮 LED 硬件操作代码 */
}

static void led_off(void) {
 /* 熄灭 LED 硬件操作代码 */
}

static int led_open(struct inode *inode, struct file *filp) {
 /* 打开设备时需要做的事情 */
}

static ssize_t led_write(struct file *filp, const char __user *buf,
 size_t size, loff_t *offt)
 {
	 int flag;
	 /* 获取应用层 write 的数据,存放在 flag 变量 */
	 if (copy_from_user(&flag, buf, size))
	 return -EFAULT;
	 /* 判断用户写入的数据,如果是 0 则熄灭 LED,如果是非 0 则点亮 LED */
	 if (flag)
		 led_on();
	 else
		 led_off();
	 return 0; 
}

static int led_release(struct inode *inode, struct file *filp) {
 /* 关闭设备时需要做的事情 */
}
static struct file_operations led_fops = {
 .owner = THIS_MODULE,
 .open = led_open,
 .write = led_write,
 .release = led_release,
};

static int led_probe(struct platform_device *pdev) {
 /* 驱动加载时需要做的事情 */
}

static int led_remove(struct platform_device *pdev) {
 /* 驱动卸载时需要做的事情 */
}

static const struct of_device_id led_of_match[] = {
{ .compatible = "alientek,led", },
 { /* sentinel */ },
};

MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
 .driver = {
 .owner = THIS_MODULE,
 .name = "led",
 .of_match_table = led_of_match,
 },
 .probe = led_probe,
 .remove = led_remove,
};
module_platform_driver(led_driver);
MODULE_DESCRIPTION("LED Driver");
MODULE_LICENSE("GPL");

以上并不是一个完整的 LED 驱动代码,如果没有接触过 Linux 驱动开发的读者,看不懂也没有关系, 并无大碍,此驱动程序使用了最基本的字符设备驱动框架编写而成,非常简单;led_fops 对象中提供了 open、 write、release 方法,当应用程序调用 open 系统调用打开此 LED 设备时会执行到 led_open 函数,当调用 close 系统调用关闭 LED 设备时会执行到 led_release 函数,而调用 write 系统调用时会执行到 led_write 函数,此驱动程序的设定是当应用层调用 write 写入 0 时熄灭 LED,write 写入非 0 时点亮 LED。

驱动程序属于内核的一部分,当操作系统启动的时候会加载驱动程序,可以看到 LED 驱动程序中仅仅实现了点亮/熄灭 LED 硬件操作相关逻辑代码,应用程序可通过 write 这个系统调用 API 函数控制 LED 亮灭;接下来我们看看 Linux 系统下的 LED 应用程序示例代码,如下所示:

复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv) {
	 int fd;
	 int data;
	 fd = open("/dev/led", O_WRONLY);//打开 LED 设备(假定 LED 的设备文件为/dev/led)
	 if (0 > fd)
		 return -1;

	 for ( ; ; ) {
		 data = 1;
		 write(fd, &data, sizeof(data)); //写 1 点亮 LED
		 sleep(1); //延时 1 秒
	
		 data = 0;
		 write(fd, &data, sizeof(data)); //写 0 熄灭 LED
		 sleep(1); //延时 1 秒
	 }
	 close(fd);
	 return 0; 
}

此应用程序也非常简单,仅只需实现用户逻辑代码即可,循环点亮、熄灭 LED,并不需要实现硬件操作相关,示例代码中调用了 open、write、close 这三个系统调用 API 接口,open 和 close 分别用于打开/关闭LED 设备,write 写入数据传给 LED 驱动,传入 0 熄灭 LED,传入非 0 点亮 LED。

LED 应用程序与 LED 驱动程序是分隔、分离的,它们单独编译,它们并不是整合在一起的,应用程序运行在操作系统之上,有操作系统支持,应用程序处于用户态,而驱动程序处于内核态,与纯粹的裸机程序存在着质的区别。Linux 应用开发与驱动开发是两个不同的方向,将来在工作当中也会负责不同的任务、解决不同的问题。

相关推荐
Yana.nice5 分钟前
openssl将证书从p7b转换为crt格式
java·linux
AI逐月10 分钟前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
想逃离铁厂的老铁12 分钟前
Day55 >> 并查集理论基础 + 107、寻找存在的路线
java·服务器
小白跃升坊37 分钟前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey1 小时前
【Linux】线程同步与互斥
linux·笔记
杨江1 小时前
seafile docker安装说明
运维
舰长1151 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
好好沉淀1 小时前
Docker开发笔记(详解)
运维·docker·容器
zmjjdank1ng1 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.1 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居