❤️你见过Linux下内核的应用程序hellowolrd吗?你说你会用c语言写hellowolrd?我不信❤️

34 篇文章 2 订阅
19 篇文章 1 订阅


一、Linux内核是什么?

Linux是一种开源电脑操作系统内核。它是一个用C语言写成,符合POSIX标准的类Unix操作系统 。操作系统是一个用来和硬件打交道并为用户程序提供一个有限服务集的低级支撑软件。一个计算机系统是一个硬件和软件的共生体,它们互相依赖,不可分割。计算机的硬件,含有外围设备、处理器、内存、硬盘和其他的电子设备组成计算机的发动机。但是没有软件来操作和控制它,自身是不能工作的。完成这个控制工作的软件就称为操作系统,在Linux的术语中被称为“内核”,也可以称为“核心”。Linux内核的主要模块(或组件)分以下几个部分:存储管理、CPU和进程管理、文件系统、设备管理和驱动、网络通信,以及系统的初始化(引导)、系统调用等。

二、如何在linux内核下写程序

  • Linux内核可以放在很多框架上,如ARM框架处理器,x86架构CPU等等。如果我们要驱动一个设备,就必须写一个驱动程序来驱动设备,而驱动程序又依赖于Linux内核,换句话来说,Linux驱动 = 软件框架 + 硬件操作。驱动程序依赖于Linux内核,你为开发板A开发驱动,那就先在Ubuntu中得到、配置、编译开发板A所使用的Linux内核。接下来教大家从0开始写一个hello world程序运行在ARM开发板上(ARM开发板也是用的Linux内核)。我们以ARM开发板IMX6ULL为例子。
  • 内核和应用层的关联。linux分为应用层编程和内核层,内核层主要是指驱动开发,应用层开发主要是网络编程,进程编程,多线程编程这些。或者再加一些QT这种应用层软件开发。内核给我们应用层提供的open/read/write等函数指针,这些函数在内核的fs.h中用一个名为file_operation结构体进行封装。驱动源码中提供真正的open、read、write、close等函数实体。我们只是通过这个结构体和应用层进行关联,这些函数都是内核本身给我们提供的,可以先不用深入了解。 在这里插入图片描述
  • 开发板的程序是如何跑起来,我们需要在linux内核下创建一个设备结点,你可以理解为通过这个设备结点来控制开发板的,如按键的操作,led点灯等。至于为什么可以通过这个设备结点来操控开发板,我们可以不用关心,我们只用知道可以提供一个驱动程序来供这个设备结点使用,
  • 总结
    ① 写一个应用层的程序,就和平常的一样
    ② 定义自己的file_operations结构体,和应用层关联起来,
    ③ 把file_operations结构体注册进内核
    ④ 创建设备结点,这个设备结点直接操控硬件,而设备节点需要驱动程序来执行
    这是比较简化的操作,其中的一些细节方面我就不一 一说明了。

三、代码编写

1.应用程序的编写
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;

	/* 1. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */ 
		write(fd, "hello world", len);
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
			
	return 0;
}

2.驱动程序的编写
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号                                                                 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
/*读函数,连接应用层的read()函数*/
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}
/*写函数,连接应用层的write()函数*,/
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}
/*打开函数,连接应用层的open()函数*,/
static int hello_drv_open (struct inode *node, struct file *file)
{
	return 0;
}


/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{
	int err;
	major = register_chrdev(0, "hello", &hello_drv); //注册file_operations 结构体
	//你只用知道下面两个函数是自动生成设备结点
	hello_class = class_create(THIS_MODULE, "hello_class");
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 ,如果不进行卸载的话驱动程序将会一直都在*/
static void __exit hello_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major, "hello");
}
/* 7. 告诉内核哪个函数是入口函数,哪个函数是出口函数 ,出口函数在卸载驱动时调用
,入口函数在驱动注册时调用 */
module_init(hello_init);
module_exit(hello_exit);
//因为Linux内核是开源的,所以所有的开发者都要遵循这个GPL协议
MODULE_LICENSE("GPL");

3.使用Makefile进行程序的编译

在你的驱动程序和应用程序下写一个makefile

/*你内核的所在目录*/
KERN_DIR = /home/book/100ask_roc-rk3399-pc/linux-4.4
all:
   //去到这个内核的目录下编译驱动模块
	make -C $(KERN_DIR) M=`pwd` modules 
	//使用交叉编译工具链进行,生成应用程序,这样就可以在arm平台下进行程序的运行了
	$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_drv_test
/要编译哪个文件?这也需要指定,设置obj-m变量即可/
obj-m	+= hello_drv.o
4.把在ubuntu生成的.ko文件和应用程序放在arm板子上执行

1.ubuntu要想把文件传输到arm平台上有很多种方式,相当于ubuntu和windows通信类似,可以使用tftp通信方式,或者NFS通信方式进行数据的传输。这里使用NFS通信

  1. 使用下面命令挂载NFS
    挂载的意思:打个比方你要挂载hello这个文件到arm上,那么你的arm上的某个目录将会有和你这个hello文件一模一样的东西,如果你修改了东西,另外一个平台上的hello文件也会修改。
    #mount -t nfs -o nolock,vers=3 192.168.1.100:/home/book/nfs_rootfs /mnt

2.在你挂载的目录下安装驱动程序:insmod hello_drv.ko ,这是将会在/dev/自动创建一个设备结点。
3.运行你的应用层程序:./hello_drv_test 。
这时候你的屏幕上将会显示应用层写入helloworld字符串。

总结

因为这其中涉及大量的准备工作,需要配置好很多的环境,所以说完成上机实验比较难,只是让大家了解一下驱动的开发流程,让大家有一个大体的了解。
我可以说这是我见过最难的hellowrold的编写,我不信你见过比这个更难得helloworld程序的编写。如果你想更深入的了解嵌入式,你可以去看韦东山老师的视频,Linux嵌入式教学第一人。
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
好的,我已经理解了您的问题。以下是一个简单的字符设备驱动程序的示例代码,包括设备打开、读、、I/O控制和关闭等基本操作。请注意,这只是一个示例,您需要根据自己的需求进行适当的修改。 chardev.c: #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "chardev" //设备名称 #define BUF_LEN 80 //缓冲区大小 static int major; //主设备号 static char msg[BUF_LEN]; //缓冲区 static char *msg_ptr; //缓冲区指针 //打开设备 static int device_open(struct inode *inode, struct file *file) { static int counter = 0; if (counter) return -EBUSY; counter++; msg_ptr = msg; try_module_get(THIS_MODULE); return 0; } //释放设备 static int device_release(struct inode *inode, struct file *file) { counter--; module_put(THIS_MODULE); return 0; } //读设备 static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) { int bytes_read = 0; if (*msg_ptr == 0) return 0; while (length && *msg_ptr) { put_user(*(msg_ptr++), buffer++); length--; bytes_read++; } return bytes_read; } //设备 static ssize_t device_write(struct file *filp, const char *buff, size_t length, loff_t *off) { int i; for (i = 0; i < length && i < BUF_LEN; i++) get_user(msg[i], buff + i); msg_ptr = msg; return i; } //I/O控制 static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case 1: printk(KERN_INFO "IOCTL command 1\n"); break; case 2: printk(KERN_INFO "IOCTL command 2\n"); break; default: return -EINVAL; } return 0; } //设备文件操作结构体 static struct file_operations fops = { .owner = THIS_MODULE, .read = device_read, .write = device_write, .unlocked_ioctl = device_ioctl, .open = device_open, .release = device_release }; //初始化字符设备 int init_chardev_module(void) { major = register_chrdev(0, DEVICE_NAME, &fops); if (major < 0) { printk(KERN_ALERT "Registering char device failed with %d\n", major); return major; } printk(KERN_INFO "Chardev module loaded with major number %d\n", major); return 0; } //关闭字符设备 void exit_chardev_module(void) { unregister_chrdev(major, DEVICE_NAME); printk(KERN_INFO "Chardev module unloaded\n"); } module_init(init_chardev_module); module_exit(exit_chardev_module); Makefile: obj-m := chardev.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 在使用前,请确定您的系统已经安装了适当的编译工具链和内核头文件。可以使用以下命令来编译和加载模块: make sudo insmod chardev.ko 您可以使用以下命令来测试设备驱动程序: echo "Hello, world!" > /dev/chardev cat /dev/chardev sudo ioctl /dev/chardev 1 希望这能帮助您了解如何编一个简单的字符设备驱动程序
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔动山霸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值