1、学习完驱动
2、复习标准IO和文件IO,知道原理,函数接口灵活使用
标准io:有fopen,fclose等,是一种c库函数,在进行系统调用前,先操作缓冲区,操作对象是文件流指针(FILE),通过库函数提供的接口进行操作。优点是可移植性好,缺点效率较低
文件io:有open,close等函数,没有缓冲区,采用系统调用的方式可以直接操作底层,与内核进行交互,操作对象是文件描述符(fd)。优点效率高,缺点则是可移植性差。
3、复习进程和线程,知道原理,函数接口灵活使用
进程:进程是执行中的程序的实例。每个进程都有一个唯一的进程标识符(PID)以及一组资源(如内存、文件描述符等)。进程是操作系统中的最小执行单位,操作系统通过管理和调度进程来实现多任务处理。
pid_t fork(void);
线程:线程是进程内的一个执行单元,是操作系统中最小的调度单位。线程共享进程的资源和内存,但每个线程拥有自己的栈空间和寄存器状态。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 成功返回0,失败返回错误码
- thread表示线程对象
- attr表示线程属性,NULL代表默认属性
- routine表示线程执行的函数(为指针函数)
- arg表示传递给routine的参数 ,参数是void * ,需要注意传递参数格式
4、复习System V通信方式,知道原理,函数接口灵活使用
- 消息队列msgget,msgsnd,msgrcv,msgctl(各种控制,如删除)
- 共享内存
- 信号量;其中消息队列和共享内存都是来完成进程通信的,而信号量主要是保证同步和互斥的;
在UNIX和类UNIX系统中,信号量是通过信号量集(semaphore set)来管理的,这些信号量集位于内核中,并且可以通过特定的键(key)来访问。当两个进程想要共享同一个信号量时,它们需要使用相同的键来访问同一个信号量集。这样,两个进程就能对同一个信号量进行操作,从而实现同步。
共享内存的通信原理:
在物理内存开辟一份共享内存的空间,然后把这份空间映射到需要进行相互通信的虚拟地址空间中;这样使得不同的进程看到了同一份资源,这样就可以通过共享内存进行通信了;
1. 创建共享内存; int shmget 和key_t ftok
在我们调用shmget函数之前,必须调用ftok来帮我们生成一个key,该key就是给shmget函数第一个参数使用了,表示表示该共享内存的唯一标识,这个key在内核中,会在共享内存的结构体,设置给共享内存的ID;
2. 挂接共享内存与进程之间的关联;shmat
3. 完成通信逻辑代码;
4. 去挂接共享内存与进程之间的关联;shmdt
5. 释放共享内存;shmctl(shmid,IPC_RMID,NULL);
示例:
5、复习线程的同步和互斥(特别是条件变量),知道原理,函数接口
互斥:
临界资源:一次只允许一个任务(进程、线程)访问的共享资源
临界区:访问临界资源的代码
互斥机制:mutex互斥锁,任务访问临界资源前申请锁,访问完后释放锁
互斥锁初始化:pthread_mutex_init()函数(动态初始化)
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t * attr);
互斥锁初始化(动态/静态)---->锁的申请和释放--->锁的销毁
读写锁:提高线程执行效率
同步:
条件变量:线程条件变量是为了解决生产者与消费者问题,是线程同步的一种手段;
必要性:为了实现等待某个资源,让线程进行休眠,从而提高运行效率;
举例taxi:
6、能保证给开发板移植操作系统
1. 环境准备
1.1 交叉编译环境搭建
- 安装交叉编译器:根据目标开发板的处理器架构(如ARM、MIPS等),安装相应的交叉编译工具链。这些工具链允许在宿主机(如PC)上编译出可在目标板上运行的代码。
- 设置环境变量:确保交叉编译器的路径被添加到系统的PATH环境变量中,以便在命令行中直接调用。
1.2 网络环境配置
- TFTP服务器:搭建TFTP服务器,用于通过网络向开发板传输内核镜像、设备树等文件。
- NFS服务器:如果计划通过网络挂载根文件系统,还需要搭建NFS服务器。
2. Bootloader移植:开源的引导加载程序
- 获取Bootloader源码:常见的Bootloader有U-Boot、GRUB等,根据开发板需求选择合适的Bootloader并获取其源码。
- 修改与编译:根据开发板的硬件配置修改Bootloader源码中的相关配置(如内存地址、串口配置等),并进行编译。
- 烧写到开发板:将编译好的Bootloader烧写到开发板的指定存储介质(如SPI Flash、SD卡等)中。
3. Linux内核移植
3.1 获取内核源码
- 从官方网站或版本控制系统(如Git)中获取Linux内核源码。
3.2 配置内核
- 修改Makefile:设置ARCH和CROSS_COMPILE变量,以指定目标架构和交叉编译器。
- 配置内核选项:使用make menuconfig、make xconfig等工具进行内核配置,选择与硬件平台相关的选项,如驱动支持、文件系统类型等。
3.3 编译内核
- 使用交叉编译器编译内核源码,生成内核镜像(如zImage、uImage等)。
3.4 烧写内核
- 将编译好的内核镜像通过TFTP服务器、串口或SD卡等方式烧写到开发板中。
4. 设备树(Device Tree)制作与移植
- 创建或修改设备树文件:根据开发板的硬件配置编写或修改设备树文件(.dts),描述硬件信息。
- 编译设备树:使用DTC(Device Tree Compiler)工具将.dts文件编译成.dtb格式的设备树二进制文件。
- 烧写设备树:将编译好的设备树文件烧写到开发板中。
5. 根文件系统制作与移植
- 创建根文件系统:可以使用BusyBox等工具创建一个简单的根文件系统,也可以从现有发行版中提取。
- 配置根文件系统:根据需要配置根文件系统中的文件、目录和权限等。
- 烧写或挂载根文件系统:将根文件系统烧写到开发板的存储介质中,或者通过网络(如NFS)挂载到开发板上。
6. 测试与调试
- 启动开发板:按照Bootloader的启动流程启动开发板,观察是否能正确加载内核、设备树和根文件系统。
- 系统测试:运行系统测试工具或脚本,检查各硬件设备是否正常工作,系统功能是否完善。
7、常见外设驱动能写出来,比如led、蜂鸣器、ADC、MPU6050等。
举例led和mpu6050:
Led:
//头文件包含
#include "leddrv.h"
#define BUF_LEN 100
//主设备号,次设备号,设备号数量设置
//定义设备结构体
struct myled_dev
{
struct cdev mydev;
unsigned int led2gpio;
unsigned int led3gpio;
unsigned int led4gpio;
unsigned int led5gpio;
};
struct myled_dev *pgmydev = NULL;
int myled_open (struct inode *pnode, struct file *pfile)//打开设备
{//}
int myled_close(struct inode *pnode, struct file *pfile)//关闭设备
{//}
/* f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平 */
void led_on(struct myled_dev *pmydev,int ledno)
{
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,1);
break;
//省略345
}
}
void led_off(struct myled_dev *pmydev,int ledno)
{
switch(ledno)
{
case 2:
gpio_set_value(pmydev->led2gpio,0);
break;
//同上
}
}
//io引脚控制,利用_IO(LED_DEV_MAGIC, 0/1)的arg来控制第二位开关
long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
struct myled_dev *pmydev = (struct myled_dev *)pfile->private_data;
if(arg < 2 || arg > 5)
{
return -1;
}
switch(cmd)
{
case MY_LED_OFF:
led_off(pmydev, arg); //关灯
break;
case MY_LED_ON:
led_on(pmydev, arg); //开灯
break;
default:
return -1;
}
return 0;
}
struct file_operations myops = {
//设置操作函数集
};
//申请gpio
void request_leds_gpio(struct myled_dev *pmydev,struct device_node *pnode)
{
//从设备树中提取gpio口 @np - 设备节点指针 @propname - 属性名 @index - gpio口引脚标号
pmydev->led2gpio = of_get_named_gpio(pnode,"led2-gpio",0);
//向内核申请GPIO
gpio_request(pmydev->led2gpio,"led2");
//345同上
}
//通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用
void set_leds_gpio_output(struct myled_dev *pmydev)
{
gpio_direction_output(pmydev->led2gpio,0);//0初始值,关灯
//345同上
}
/*
功能:解除io管脚的映射
参数:addr:io管脚映射的地址
*/
void free_leds_gpio(struct myled_dev *pmydev)
{
gpio_free(pmydev->led2gpio);
//345同上
}
int myled_probe(struct platform_device *p_pltdev)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
struct device_node *pnode = NULL;//设置pnode,设备节点信息
//手动,自动申请设备号
//为struct myled_dev结构体分配内存,并确保在使用之前该内存区域被清零
pgmydev = (struct myled_dev *)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
if(NULL == pgmydev)
{
unregister_chrdev_region(devno, char_num);
printk("kmalloc for 'struct myled_dev' failed\n");
return -1;
}
memset(pgmydev, 0, sizeof(struct myled_dev));
/* 给struct cdev对象指定操作函数集 */
cdev_init(&pgmydev->mydev, &myops);
/* 将struct cdev对象添加到内核对应的数据结构中 */
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev, devno, char_num);
pnode = p_pltdev->dev.of_node;//设备树里面对应的
/* ioremap */
request_leds_gpio(pgmydev,pnode);
/* con-register set output */
set_leds_gpio_output(pgmydev);
return 0;
}
int myled_remove(struct platform_device *p_pltdev)
{
dev_t devno = MKDEV(major, minor);
/* 从内核中移除一个字符设备 */
cdev_del(&pgmydev->mydev);
/* 回收设备号 */
unregister_chrdev_region(devno, char_num);
/* iounmap 解除io管脚的映射 addr:io管脚映射的地址*/
free_leds_gpio(pgmydev);
/* 释放内存 */
kfree(pgmydev);
pgmydev = NULL;
return 0;
}
//使用设备树(Device Tree)来匹配和配置硬件设备
struct of_device_id myleddev_of_ids[] =
{
[0] = {.compatible = "fs4412,led2-5"},
[1] = {.compatible = "fs4412,key2"},
[2] = {},
};
struct platform_driver myled_driver =
{
.driver = {
.name = "fs4412leds",
.of_match_table = myleddev_of_ids,
},
.probe = myled_probe,//设备和驱动匹配成功之后调用该函数
.remove = myled_remove,//设备卸载了调用该函数
};
//基础init,exit等配置
MPU6050:
http://t.csdnimg.cn/nkV1v