嵌入式系统原理与应用——基于Linux和ARM笔记整理

嵌入式系统基础

高内聚低耦合的含义是什么?

Bootloader的含义及作用:

思考题:

对于身边的嵌入式设备,查找资料,分析处理器体系类型,外设种类操作系统等并绘制系统结构图。
为什么现在的嵌入式系统中一般都带有操作系统?操作系统带来哪些好处?会不会带来额外的负担?你了解哪些国产嵌入式操作系统?
高级程序语言编写的程序转换成可执行代码需要经过哪几个步骤?为什么?
分析c语言程序中的常量全局变量局部变量在运行中分别存放在哪些存储空间中,对内存有何影响?
嵌入式系统软件开发过程中为什么要采用交叉开发模式调试的通信端口主要有哪些?分别用在哪种调试模式中?
查找资料,了解Linux软件开发工具主要有哪些作用,分别是什么?
如何搭建linux的开发环境
要进行裸机开发首先要先搭建好开发环境,我们在开始学习 STM32 的时候需要安装 一堆
的软件,比如 MDK IAR 、串口调试助手等等,这个就是 STM32 的开发环境搭建。同样 的,
要想在 Ubuntu 下进行 Cortex-A7/A9(I.MX6U) 开发也需要安装一些软件,也就是网上说的开发环
境搭建,环境搭建好以后我们就可以进行开发了。
Ubuntu 交叉编译工具链安装
安装 Vim 编辑器
首先执行 sudo apt update 更新系统源,
然后执行 sudo apt-get install vim-gtk
最后让 VIM 功能显示行数,执行 vi ~/.vimrc ,按 i 键表示输入,输入 set number 保存并退出。

 

安装交叉编译器
在Ubuntu中创建目录: /usr/local/arm
gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 放在/usr/local/arm
拷贝完成以后在 /usr/local/arm 目录中对交叉编译工具进行解压
修改环境变量,使用VI打开 /etc/profile 文件
打开 /etc/profile 以后,在最后面输入如下所示内容:export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
安装相关库

使用交叉编译器之前还需要安装一下其它的库

查看一下交叉编译工具的版本号
使用 Ubuntu 自带的 GCC 编译器,我们用的是命令 “gcc” 。要使用刚刚安装的交
叉编译器的时候使用的命令是 “arm-linux-gnueabihf-gcc” “arm-linux-gnueabihf- gcc” 的含义如下:
1 arm 表示这是编译 arm 架构代码的编译。
2 linux 表示运行在 linux 环境下。
3 gnueabihf 表示嵌入式二进制接口。
4 gcc 表示是 gcc 工具。

ARM处理器体系架构

处理器指令执行过程还是太抽象了,请给一种比较通俗的解释:、

思考题:

 处理器程序执行是如何?程序执行过程中需要哪些寄存器和存储器的参与?

处理器指令执行过程(通俗可以理解为让指令与数据结合,发生化学反应的过程)包括:

取值 从IR中取出指令

译码 解释指令

执行 读取数据

存储 保存执行结果

复杂指令集与精简指令集的优缺点各有哪些?为什么复杂指令集仍在大量使用?
 Im处理器中通用寄存器有哪些鸽子的作用是什么?特殊功能寄存器有哪些?作用是什么?为什么要设置备份寄存器? 
对比分析大端与小端数据存储格式。查询资料,说明在网络通信中数据格式采用的是大端方式还是小端方式
说明内存映射的主要作用,分析ARM处理外设的寄存器定义方式及其作用。如何定义拓展外设的寄存器?
结合异常向量表,分析ARM处理器启动过程程序执行流程
分析中断相应的基本条件和中断处理的执行流程,并分析不同外部中断请求中断源的识别过程

Linux操作系统基础知识

可以保存部分程序和数据的所谓“外部介质”是什么。

“接口”是什么?接口除了有编程级的接口外,还有什么别的级别吗?

什么是OEM和OEM板机厂商?

什么是“UNIX”哲学?

“基于Flash”的文件系统中FLash是什么?

已知fs代表文件系统,那么procfs,devfs,susfs的英文全称及其含义是什么?

Linux模块机制有点难,试着用更形象简单的语言解释他

linux中“镜像”的含义是什么?

linux中uid和suid的英文全称及其含义:

文件操作函数实例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ types.h>
#include <sys/stat.h>
#include <errno.h>
#define BUFFER SIZE 128    //每次读写缓存 大小,影响运行效率
#define SRC_FILE_NAME "src_file.txt"    //源文件名
#define DEST_FILE_NAME "dest_file.txt"    //目标文件名
#define OFFSET 0    //文件指针偏移量

int main (){
    int sre_file, dest_file;
    unsigned char src_ buff[BUFFER_SIZE] ;
    unsigned char dest_ buff[BUFFER_ SIZE];
    int reaL_ read_ len = 0;
    char str[BUFFER_ SIZE] = "this is a testabout\nopen()\nclose()\nwrite()\nread()\nlseek()\nend of the file\n";    //创建源文件
    src_ file=open(SRC_ FILE_NAME,O_RDWR|0_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_ IROTH);
    if(sre_ file<0)
    {
        printf ("create file error!!!\n") ;
        exit(1);
    }
    //向源文件中写数据
    write(src_file, str, sizeof(str));
    //创建目的文件
    dest_ file=open(DEST_FILE_NAME,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if (dest_ file<0)
    {
        printf("open file error!!!\n");
        exit (1) ;
    }
    1seek (sre_ file, OFFSET, SEEK_SET) ;//将源文件的读写指针移到起始位置
    while(real_read_len=read(sre_file,src_buff,sizeof (src_buff)))>0)
    {
        printf("src_file:%s",src_buff);
        write(dest_file,src_buff, real_rea_len);
        1seek (dest_file, OFFSET, SEK_ SET);//将目的文件的读写指针移到起始位置
    }
    while((real_ read_len=read (dest file, dest_ buff,sizeof (dest_buff))>0);//读取目的文件的内容
    printf("dest_file:%s",dest_ buff);
    close(src_file);
    close (dest_file) ;
    return 0;
}

思考题:

在理解操作系统的主要功能后,分析基于优先级的多任务调度策略和工作原理。
试分析LInux文件系统的主要作用和工作原理。应用层调用文件系统如何访问硬件设备?
编写一个内核模块,实现模块的注册、模块运行打印输出、模块的卸载,通过控制台打印输出该模块的操作流程。
#include <linux/init.h>		//所有模块都会需要这个头文件
#include <linux/module.h>	//下面的宏需要

MODULE_LICENSE("Dual BSD/GPL"); //告诉内核,该模块采用自由许可证
static int __init print_init(void)//模块被袁载到内核时调用
{
	printk(KERIN_ALERT"init successfully\n"); //函数printk在umn内核中定义,功能和标准C库中的函数printf类似
	return 0;
}

static void __exit print_exit(void)//模块被移除出内核时调用
{
	printk (KERN ALERT "exit successfully\n");
	return 0;
}

module_init (print_init);//模块加载函数
module_exit(print_exit);//模块 卸载函数

ARM体系结构的Linux内核

其他问题

内存分配与重定位分别是什么含义?

“接口”的本质是不是就是函数?

Linux中协议栈指的是什么?

任务号与排期、下发任务之间有什么关系?排期的过程是什么?

任务下发和展示如何区分线程级和进程级等?

Linux调度器是如何以模块方式提供的呢?

TLB中如果没有虚拟地址的入口时,“转换表遍历硬件”是什么样的操作?“从存放在内存的转换表中获得转换和访问器权限”又是什么操作?

什么是底层的Buddy算法?

Linux中的mount操作是什么?

消息队列标识符和IPC标识符分别是什么?

锁机制是什么?为什么说信号量可以被用作一个锁机制?

思考题
分析Linux进程的特点,Linux进程管理是以哪个数据结构为基础的?进程间的状态转换条件有哪些?实时调度与非实时调度策略主要区别在哪些地方?
 ARM_Linux的内存管理是如何实现的?主要目的是什么?如何在具有内存管理功能的操作系统中访问外设寄存器?
在Linux中,哪些设备的访问是通过虚拟文件系统的?哪些不是通过虚拟文件系统?
在进程通讯中,进程间短信息与事件传输主要采用哪些通信方式?数据量大的信息交换有哪些通信方式?
在嵌入式Linux中创建四个进程,2个实时进程,2个非实时进程。每个进程中创建两个线程,在线程中调用系统服务来测试任务可中断睡眠、不可中断睡眠、暂停3中状态的切换,用一个 进程来测试进程退出过程。 要求: 1.说明进程创建与线程创建过程; 2.分别说明你在不同进程状态切换中使用的系统调用是哪种; 3.通过串口输出进程状态切换的打印信息,并截图插入作业报告中。

在完成任务前,先来理一下基本的原理及其代码

fork工作原理实例:

#include <sys/types.h>
#include <unistd.h>
#include <std1o.h>
Hinclude <stdlib.h>
int main (vo1d){
	p1d_ t pid; 
	char message ;
	int n;
	pid=fork();
	if (pid < 0)
	{
		perror ("fork falled");
		exit(1);
	}
	if(pid==0)
	{
		printf("This 1s the child process. My PID is; d. My PPID 18: %d.\n",getpid(),getppid()) ;
	}
	else
	{
		printf ("This is the parent process. My PID is 8d. \n", getpid());
	}
	return 0;
}

已知:

  1. Pid=-1时,说明进程创建失败;
  2. Pid=0时,说明该进程是子进程;
  3. Pid>0时,说明该进程是父进程。

vfork工作原理实例

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
struct sched_param param; //调度策略参数
int main()
{
    pid_t pid;
    int cnt = 0;
    pid = vfork();
    if(pid > 0)
    {
        while(1)
        {
            printf("this is father pid = %d\n",getpid());
            sleep(2);
        }
    }
    else if(pid == 0)
    {
        if (sched_setscheduler(getpid(), SCHED_FIFO, &param) == -1)
        {
            perror("sched_setscheduler");
            exit(EXIT_FAILURE);
        }
        while(1)
        {
            printf("this is person pid,person pid=%d\n",getpid());
            sleep(2);
            cnt++;
            if(cnt == 3)
            {
                exit(0);
            }
        }
    }
    return 0;
}

exec函数族成员工作原理应用实例:

//exec让当前进程执行指定的程序,通常fork之后在子进程中使用。exec 函数一旦调用成功即执行新的程序,不返回。只有失败才返回 -1。
//资源调度就结果来说,算是“换核不换壳”
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
//6个函数都是在exec的基础上加上几个字母作为函数名。添加字母和对应含义如下:
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

我们结合上述三个工作原理实例,完成下面“进程指定执行程序的实例”

首先创建一个所谓”要执行的程序“,即一个.cpp文件,然后使用gcc编译器编译这个源程序文件,并取名为multiexec

// multiexec.cpp
#include<iostream> 
#include<cstdio> 
int main(int argc, char* argv[])
{ 
    std::cout << __FILE__ << " " << __func__ << " " << __LINE__ << std::endl;
    if(argc > 1) 
    { 
        for(int i = 0; i < argc; ++i)
        { 
        printf("argv[%d]: %s\n", i, argv[i]); 
        } 
    } 
    return 0; 
}

要执行的程序multiexec创建好后,就要再写一个程序,使进程指定multiexec进行执行操作

// multiprocess.cpp
#include<iostream>
#include<unistd.h>
#include<cstdio>
int main()
{
    pid_t pid = fork(); 
    if( 0 == pid ) 
    { 
        std::cout << "hi, I'am a child process and the process id is " << getpid()<<std::endl; 
        execl("./multiexec","multiexec","arg1","arg2",NULL); //执行全新代码
    }
    else if(pid > 0)
    { 
        std::cout << "hi, I'am a parent process and the process id is " << getpid() <<std::endl; 
    } 
return 0; 
}

进程相关的基础代码就到这里了,下面开始进入线程的基础代码实例

pthread的基础API:

/**************初始化与销毁线程对象***********/
(1)初始化一个线程对象的属性
函数原型:
int pthread_attr_init(pthread_attr_t *attr);
若函数调用成功返回0,否则返回对应的错误代码。
attr:指向一个线程属性的指针
(2)销毁一个线程属性对象
pthread_attr_destroy()函数销毁。
函数原型:
int pthread_attr_destroy(pthread_attr_t *attr);
若函数调用成功返回0,否则返回对应的错误代码。
attr:指向一个线程属性的指针


/*****************创建与销毁线程****************/
pthread_create()函数是用于创建一个线程的,创建线程实际上就是确定调
用该线程函数的入口点。
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void 
*(*start_routine) (void *), void *arg);
参数说明:
1) pthread_t *thread:pthread_t是一种用于表示线程的数据类型,每一个pthread_t类型的变量都可以表示一个线程。
2) const pthread_attr_t *attr:用于手动设置新建线程的属性,例如线程的调用策略、线程所能使用的栈内存的大小等。
3) void *(*start_routine) (void *):以函数指针的方式指明新建线程需要执行的函数。
4)void *arg:指定传递给start_routine()函数的实参,当不需要传递任何数据时,将arg赋值为NULL即可。

void pthread_exit(void *retval);
//retval:如果retval不为空,则会将线程的退出值保存到retval中,如果不关心线程的退出值,形参为NULL即可

pthread_join()
//数以阻塞的方式等待thread指定的线程结束,并且获取它的退出值


/***************线程调度相关的API,看不懂就不必看了**************/
policy:可选值为线程的三种调度策略,SCHED_OTHER、SCHED_FIFO、SCHED_RR。
POSIX标准指定了三种调度策略:
➢ 分时调度策略 (SCHED_OTHER)。
➢ 实时调度策略,先进先出方式调度(SCHED_FIFO)。
➢ 实时调度策略 ,时间片轮转方式调度(SCHED_RR)。

pthread工作原理实例:

void *test_thread(void *arg) /* 线程执行代码 */
{
    int num = (unsigned long long)arg;
    printf("arg is %d\n", num);
    while(1) {
        ……
        if( ?? )
            pthread_exit(NULL);
    }
}


int main(void)
{
    pthread_t thread;
    void *thread_return;
    int arg = 520;   //这个arg的含义以后会详细解释
    int res;
//尝试创建一个线程
    printf("start create thread\n");
    res = pthread_create(&thread, NULL,test_thread, (void*)(unsigned long long)(arg));
    if(res != 0)
    {
        printf("create thread fail\n");
        exit(res);
    }
    printf("create treads success\n");

//以阻塞的方式等待刚创建的线程退出
    printf("waiting for threads to finish...\n");
    res = pthread_join(thread,&thread_return);
    if(res != 0)
    {
        printf("thread exit fail\n");
        exit(res);
    }
    printf("thread exit ok!\n");
    return 0;
}

现在我们正式开始任务的完成:

1.导入所有的库函数。这里导入的库函数一定是过多的,但做实验的话还是通用性比较重要,以后也直接将所有库函数导入完就好了。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>

#include<pthread.h>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sched.h>

2.定义创建进程的函数。我们在创建进程时不调用set_realtime_scheduler()函数。这样,新创建的进程将继承父进程的调度策略,即默认的非实时策略。而设置调度策略为实施策略的函数我将在下一步讲解

/************ 创建非实时进程********************/
void create_nonrealtime_processes() {
    pid_t pid = fork();
    if (pid == 0) {
	printf("pid:%d   ",getpid());  //打印自己的pid
        printf("Create the first non-real-time process successfully\n");
        exit(0);
    }
    else if (pid > 0) {
        pid = fork();
        if (pid == 0) {
            printf("Create the second non-real-time process successfully\n");
            exit(0);
        }
    }
}

3.因为要创建实时进程,所以要定义函数来设置进程调度策略为实时策略

/**********************创建real-time进程*******************/
//创建进程
void create_processes() {
    pid_t pid;
    int i;

    for (i = 0; i < 2; i++)   //循环两次,表示每个进程创建两个子进程
    {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("pid1:%d   \n",getpid());  //打印自己的pid
            //create_threads();  //创建线程的函数
	printf("create real-time process successfully\n");
            break;
        } else if (pid < 0) {
            // 出错处理
            perror("fork() error");
            break;
        }
    }
}

// 设置进程调度策略为实时策略
void set_realtime_scheduler() {
    struct sched_param param;
    param.sched_priority = 99; // 设置实时优先级,取值范围为1-99

    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler() error");
    }
}

// 创建实时进程
void create_realtime_processes() {
	// 创建实时进程
    set_realtime_scheduler();
    create_processes();
	
    exit(0);
}

在主函数中,我写到:

// 创建非实时进程
    create_nonrealtime_processes();
    
    // 创建实时进程
    set_realtime_scheduler();
    create_realtime_processes();

在测试创建进程的任务中,先将所有create_thread相关代码注释掉,得到测试结果。可见,两个实时进程和非实时进程创建成功。测试代码为“work2_1.cpp”

4.定义创建线程的函数

// 创建线程
void create_threads() {
    pthread_t threads[2];
    int i;
    for (i = 0; i < 2; i++) {
        pthread_create(&threads[i], NULL, thread_function, NULL);
        //这句语句中的thread_function是一个函数,相当于创建这个线程时所要实现的功能。这个函数会在下面定义
    }
}

5.定义线程的功能函数

首先是任务可中断睡眠。"任务可中断睡眠"指进程进入睡眠状态,可以被外部中断唤醒。在 Linux 系统中,可以使用 sleep 或 usleep 系统调用来实现可中断睡眠。例如,sleep(5) 表示进程睡眠 5 秒钟。如果在睡眠期间接收到信号,进程会被唤醒。

// 线程函数
void* thread_function1(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after sleep”
        //任务可中断睡眠
        sleep(1);
        printf("Task is awake after a second sleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

然后是任务不可中断睡眠。"任务不可中断睡眠"指进程进入睡眠状态,无法被外部中断唤醒。在 Linux 系统中,可以使用 pause 系统调用来实现不可中断睡眠。进程会一直睡眠,直到接收到一个信号。通常,用于等待信号的时候会使用 pause 函数。

// 线程函数
void* thread_function2(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after usleep”
        //任务不可中断睡眠
        usleep(10);
        pause();
        
        printf("Task is awake after usleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

最后是暂停。"任务暂停"指进程的执行被暂停,直到接收到某个特定的信号。在 Linux 系统中,可以使用 pause 或 sigsuspend 系统调用来实现任务暂停。调用这些函数后,进程将一直等待,直到接收到一个指定的信号才会继续执行。

// 线程函数
void* thread_function3(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他暂停后打印“Task is awake after pause”
        //暂停
        pause();
        printf("Task is awake after pause");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

下面就休眠来做个实验,创建以上两个线程后,分别让“可中断线程”休眠1秒和5秒,观察终端的输出结果:

 休眠1秒时,tid1比tid2先创建,且不可中断睡眠并没有执行在pause之后的printf语句

休眠5秒时,tid2比tid1先创建

从线程创建的个数可以看到,创建线程的过程被pause中断了,需要在外部中断或其他方式将其唤醒。测试代码为work2_2.cpp

6.任务要求用一个进程来测试进程退出过程,我将在主函数的最开头写入如下代码

// 设置一个进程来测试进程退出过程
    pid_t pid = fork();
    if (pid == 0) 
    {
        // 子进程
        while (1) {
        // 进程中执行的任务,这里我什么也不执行
        // TODO: 在这里进行进程退出过程的测试

            // 退出进程并输出消息
            printf("Process exits successfully\n");
            exit(0);
        }
    }

以上代码的含义是:子进程将无限循环执行任务,然后调用exit(0)函数退出进程,并在退出处输出"Process exits successfully"的消息。注意在使用gcc编译包换线程相关内容的c++代码时,请务必加上“-lpthread”,以下为测试成功截图,测试代码为“work2.cpp”:

最后的最后,我将所有测试代码的源码放在这里

//work2.cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>

#include<pthread.h>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sched.h>

void create_threads();
void create_process();
void set_realtime_scheduler();
void* thread_function1(void* arg);
void* thread_function2(void* arg);
void* thread_function3(void* arg);




// 创建线程
void create_threads() {
    pthread_t threads[2];
    int i;
    for (i = 0; i <= 2; i++) {
	if(i==0)
        	pthread_create(&threads[i], NULL, thread_function1, NULL);
	else if(i==1)
		pthread_create(&threads[i], NULL, thread_function2, NULL);
	else
		pthread_create(&threads[i], NULL, thread_function3, NULL);
        //这句语句中的thread_function是函数,相当于创建这个线程时所要实现的功能。这个函数会在下面定义
    }
}

// 线程函数
void* thread_function1(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after sleep”
        //任务可中断睡眠
        sleep(1);
        printf("Task is awake after a second sleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function2(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after usleep”
        //任务不可中断睡眠
        usleep(10000);
        
        printf("Task is awake after usleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function3(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他暂停后打印“Task is awake after pause”
        //暂停
        pause();
        printf("Task is awake after pause");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

//创建进程
void create_processes() {
    pid_t pid;
    int i;

    for (i = 0; i < 2; i++)   //循环两次,表示每个进程创建两个子进程
    {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("pid1:%d\n   ",getpid());  //打印自己的pid
            //create_threads();  //创建线程的函数
            break;
        } else if (pid < 0) {
            // 出错处理
            perror("fork() error");
		printf("\n");
            break;
        }
    }
}

// 设置进程调度策略为实时策略
void set_realtime_scheduler() {
    struct sched_param param;
    param.sched_priority = 99; // 设置实时优先级,取值范围为1-99

    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler() error");
    }
}



int main() {

// 设置一个进程来测试进程退出过程
    pid_t pid = fork();
    if (pid == 0) 
    {
        // 子进程
        while (1) {
        // 进程中执行的任务,这里我什么也不执行
        // TODO: 在这里进行进程退出过程的测试

            // 退出进程并输出消息
            printf("Process exits successfully\n");
            exit(0);
        }
    }

}
//work2_1.cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>

#include<pthread.h>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sched.h>

void create_nonrealtime_processes();
void create_realtime_processes();
void create_threads();
void create_process();
void set_realtime_scheduler();
void* thread_function1(void* arg);
void* thread_function2(void* arg);
void* thread_function3(void* arg);
/************ 创建非实时进程********************/
void create_nonrealtime_processes() {
    pid_t pid = fork();
    if (pid == 0) {
	printf("pid:%d   \n",getpid());  //打印自己的pid
        printf("Create the first non-real-time process successfully\n");
        exit(0);
    }
    else if (pid > 0) {
        pid = fork();
        if (pid == 0) {
            printf("Create the second non-real-time process successfully\n");
            exit(0);
        }
    }
}

/**********************创建real-time进程*******************/
void create_processes() {
    pid_t pid;
    int i;

    for (i = 0; i < 2; i++)   //循环两次,表示每个进程创建两个子进程
    {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("pid1:%d   \n",getpid());  //打印自己的pid
            create_threads();  //创建线程的函数
	printf("create real-time process successfully\n");
            break;
        } else if (pid < 0) {
            // 出错处理
            perror("fork() error");
            break;
        }
    }
}
// 设置进程调度策略为实时策略
void set_realtime_scheduler() {
    struct sched_param param;
    param.sched_priority = 99; // 设置实时优先级,取值范围为1-99

    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler() error");
    }
}
// 创建实时进程
void create_realtime_processes() {
	// 创建实时进程
    set_realtime_scheduler();
    create_processes();
	
    exit(0);
}



/********************thread relevant*********************/
// 创建线程
void create_threads() {
    pthread_t threads[2];
    int i;
    for (i = 0; i <= 2; i++) {
	if(i==0)
        	pthread_create(&threads[i], NULL, thread_function1, NULL);
	else if(i==1)
		pthread_create(&threads[i], NULL, thread_function2, NULL);
	else
		pthread_create(&threads[i], NULL, thread_function3, NULL);
        //这句语句中的thread_function是函数,相当于创建这个线程时所要实现的功能。这个函数会在下面定义
    }
}

// 线程函数
void* thread_function1(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after sleep”
        //任务可中断睡眠
        sleep(1);
        printf("Task is awake after a second sleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function2(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after usleep”
        //任务不可中断睡眠
        usleep(10000);
        
        printf("Task is awake after usleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function3(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他暂停后打印“Task is awake after pause”
        //暂停
        pause();
        printf("Task is awake after pause");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}
int main() {
    // 创建非实时进程
    create_nonrealtime_processes();
    
    // 创建实时进程
    set_realtime_scheduler();
    create_realtime_processes();

    return 0;
}

//work2_2.cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>

#include<pthread.h>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sched.h>

void create_realtime_processes();
void create_threads();
void create_process();
void set_realtime_scheduler();
void* thread_function1(void* arg);
void* thread_function2(void* arg);
void* thread_function3(void* arg);

/**********************创建real-time进程*******************/
void create_processes() {
    pid_t pid;
    int i;

    for (i = 0; i < 2; i++)   //循环两次,表示每个进程创建两个子进程
    {
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("pid1:%d   \n",getpid());  //打印自己的pid
            create_threads();  //创建线程的函数
		printf("create real-time process successfully\n");
            break;
        } else if (pid < 0) {
            // 出错处理
            perror("fork() error");
            break;
        }
    }
}
// 设置进程调度策略为实时策略
void set_realtime_scheduler() {
    struct sched_param param;
    param.sched_priority = 99; // 设置实时优先级,取值范围为1-99

    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler() error");
    }
}
// 创建实时进程
void create_realtime_processes() {
	// 创建实时进程
    set_realtime_scheduler();
    create_processes();
	
    exit(0);
}



/********************thread relevant*********************/
// 创建线程
void create_threads() {
    pthread_t threads[2];
    int i;
    for (i = 0; i <= 2; i++) {
	if(i==0)
        	pthread_create(&threads[i], NULL, thread_function1, NULL);
	else if(i==1)
		pthread_create(&threads[i], NULL, thread_function2, NULL);
	else
		pthread_create(&threads[i], NULL, thread_function3, NULL);
        //这句语句中的thread_function是函数,相当于创建这个线程时所要实现的功能。这个函数会在下面定义
    }
}

// 线程函数
void* thread_function1(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after sleep”
        //任务可中断睡眠
        sleep(1);
        printf("Task is awake after a second sleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function2(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他休眠一秒后打印“Task is awake after usleep”
        //任务不可中断睡眠
        usleep(10);
        
        printf("Task is awake after usleep");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}

// 线程函数
void* thread_function3(void* arg) {
    // 线程中只执行一次的任务,这里我让他打印自己的tid
    printf("pthread_tid = %lu\n",(unsigned long)pthread_self());

    while (1) {
        // 线程中可以循环执行的任务,这里我让他暂停后打印“Task is awake after pause”
        //暂停
        pause();
        printf("Task is awake after pause");
        // TODO: 在这里调用系统服务进行测试
    }
    return NULL;
}
int main() {
	while(1){
	    create_realtime_processes();
	}

    return 0;
}

编写应用程序,该程序包含三个进程,每个进程中包含两个线程 采用共享内存、套接字、信号量等通信方式实现进程间的通信、同步、互斥操作。

完成这个任务前,先来看一下一些常用的函数:

建立管道的标准函数

//建立管道,该函数在数组上填上两个新的文件描述符后返回0,失败返回-1。
int pipe(int file_descriptor[2]);
eg.int fd[2];int result = pipe(fd);
//通过使用底层的read和write调用来访问数据。向file_descriptor[1]写数据,从file_descriptor[0]中读数据。写入与读取的顺序原则是先进先出。

建立命名管道的标准函数

int mkfifo(const char *filename,mode_t mode); 
//建立一个名字为filename的命名管道,参数mode为该文件的权限(mode%~umask),若成功则返回0,否则返回-1,错误原因存于errno中。
例如:mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
具体操作方法只要创建了一个命名管道然后就可以使用open、read、write等
系统调用来操作。创建可以手工创建或者程序中创建。
int mknod(const char *path, mode_t mode, dev_t dev); 
//第一个参数表示你要创建的文件的名称,第二个参数表示文件类型,第三个参数表示该文件对应的设备文件的设备号。只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可

进程对信号响应的多个接口函数

void (*signal(int sig,void (*func)(int)))(int); 
//用于截取系统信号,第一个参数为信号,第二个参数为对此信号挂接用户自己的处理函数指针。返回值为以前信号处理程序的指针。
eg.int ret = signal(SIGSTOP, sig_handle);
int kill(pid_tpid,int sig);
 //kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。
int raise(int sig);//向当前进程中自举一个信号sig, 即向当前进程发送信号。
unsigned int alarm(unsigned int seconds); //alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。
int pause(void);
//使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数

实现消息队列的多个接口函数

在命名管道中,发送数据用write,接收数据用read,则在消息队列中,
发送数据用msgsnd,接收数据用msgrcv。
➢ msgget() 创建或打开消息队列函数,这里创建的消息队列的数量会受到系统消息队列数量的限制;
➢ msgsnd()添加消息函数,它把消息添加到已打开的消息队列末尾;
➢ msgrcv()读取消息函数,它把消息从消息队列中取走,与FIFO不同的是,这里可以取走指定的某一条消息;
➢ msgctl()控制消息队列函数,它可以完成多项功能

实现信号量的多个接口函数

sem_open();  //有名信号量
sem_init(); //无名信号量(基于内存的信号量)

sem_wait();
sem_trywait();
sem_post();
sem_getvalue();

sem_close();
sem_unlink();

sem_destroy();
➢ int semget(key_t key, int num_sems, int sem_flags); 
//semget函数用于创建一个新的信号量集合,或者访问一个现有的集合(不同进程只要key值相同即可访问同一信号量集合)。第一个参数key是ftok生成的键值,第二个参数num_sems可以指定在新的集合应该创建的信号量的数目,第三个参数sem_flags是打开信号量的方式。
//ftok是什么?
➢ int semop(int sem_id, struct sembuf *sem_ops, size_tnum_sem_ops); 
//semop函数用于改变信号量的值。第二个参数是要在信号集合上执行操作的一个数组,第三个参数是该数组操作的个数 。
➢ int semctl(int sem_id, int sem_num, int command,...); 
//semctl函数用于信号量集合执行控制操作,初始化信号量的值,删除一个信号量等。 类似于调用msgctl(), msgctl()是用于消息队列上的操作。第一个参数是指定的信号量集合(semget的返回值),第二个参数是要执行操作的信号量在集合中的索引值(例如集合中第一个信号量下标为0),第三个command参数代表要在集合上执行的命令。

实现内存映射的多个接口函数

void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); 
//mmap函数将一个文件或者其它对象映射进内存。 第一个参数为映射区的开始地址,设置为0表示由系统决定映射区的起始地址;第二个参数为映射的长度;第三个参数为期望的内存保护标志;第四个参数是指定映射对象的类型;第五个参数为文件描述符(指明要映射的文件);第六个参数是被映射对象内容的起点。成功返回被映射区的指针,失败返回MAP_FAILED[其值为(void *)-1]。
int munmap(void* start, size_t length); 
//munmap函数用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。如果解除映射成功则返回0,否则返回-1,错误原因存于errno中错误代码EINVAL。
int msync(void *addr,size_t len,int flags); 
//msync函数实现磁盘文件内容和共享内存取内容一致,即同步。第一个参数为文件映射到进程空间的地址,第二个参数为映射空间的大小,第三个参数为刷新的参数设置

套接字连接过程所使用的函数

/**************************以下是一个客户端向服务端发送数据的一般流程********************/
/**************server服务端的一般流程***************/
//创建socket套接字
int socket(int domain, int type, int protocal); 
//绑定端口号
int bind(int socket, const struct sockaddr *address, size_taddress_len); 
//监听该端口号
int listen(int socket, int backlog); 
//接收来自客户端的连接请求
int accept(int socket, struct sockaddr *address, size_t *address_len); 
//从socket中读取数据
int recv();
//关闭套接字
close();

/**************client客户端的一般流程***************/
//创建socket套接字
int socket(int domain, int type, int protocal); 
//连接指定计算机端口
int connect(int socket,const struct sockaddr *addrsss, size_t address_len);
//向socket中写入数据
int send();
//创建socket套接字
int socket(int domain, int type, int protocal); 

导入库函数

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

声明所有线程函数

//declare call thread function
void *fun1(void *);
void *fun2(void *);
void *fun3(void *);
void *fun4(void *);
void *fun5(void *);
void *fun6(void *);

3个进程,每个进程中包含2个线程

	pid_t pid1,pid2,pid3;
	int ret;
        pid1=fork();
        printf("pid1:%d   ",getpid());
        printf("the return value:%d\n",pid1);
	pthread_t tid1,tid2;
	ret = pthread_create(&tid1,NULL,fun1,NULL);
	ret = pthread_create(&tid2,NULL,fun2,NULL);

        pid2=fork();
        printf("pid2:%d   ",getpid());
        printf("the return value:%d\n",pid2);
	pthread_t tid3,tid4;
	ret = pthread_create(&tid3,NULL,fun3,NULL);
	ret = pthread_create(&tid4,NULL,fun4,NULL);

        pid3=fork();
        printf("pid3:%d   ",getpid());
        printf("the return value:%d\n",pid3);
	pthread_t tid5,tid6;
	ret = pthread_create(&tid5,NULL,fun5,NULL);
	ret = pthread_create(&tid6,NULL,fun6,NULL);

每个线程函数的内部如下:

void *fun1(void *arg)
{
        printf("pthread_tid1 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}
 
void *fun2(void *arg)
{
        printf("pthread_tid2 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}
 
void *fun3(void *arg)
{
        printf("pthread_tid3 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}
 
void *fun4(void *arg)
{
        printf("pthread_tid4 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}
 
void *fun5(void *arg)
{
        printf("pthread_tid5 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}
 
void *fun6(void *arg)
{
        printf("pthread_tid6 = %lu\n",(unsigned long)pthread_self());
	pthread_exit(NULL);
}

Linux设备驱动程序结构

其他问题

/proc是什么样的一个文件系统?什么是文件系统?

思考题

分析Linux总线、设备、驱动程序的关系,分析外设驱动程序的中断处理与驱动程序之间如何进行关联
选择一个典型的外设驱动程序,分析platform总线方式管理驱动程序与传统的驱动程序管理方式的异同点

一个明显的优势在于platform机制将设备本身的资源注册及进核,由内核统一管理。在驱动程序中使用这些资源是通过platform device提供的标准接口进行申请并使用。

查找资料分析,在系统启动时设备处查找与挂载设备的流程。

字符设备驱动程序与应用案例

其他问题

思考题

查看开发板GPIO的数据手册,编写一个基于GPIO的字符驱动程序,要求使用设备树中断操作,并分析说明驱动程序函数的作用。
参考led底层驱动,编写简单的uvrt底层驱动程序,并分析uart串口驱动程序中应用程序如何通过read(),write(),ioctrl等函数操作传递调动底层的驱动程序函数。 

首先我们先了解一下基本例程

字符设备驱动模块加载和卸载函数模板

strruct xxx_dev_t{
    struct cdev cdev;
    ```
}xxx dev
/*设备驱动模块加载函数*/
static int_init xxx_ init(void)
{
    ```
    cdev_init(&XXX_ dev.cdev,&xxx_fops) ;
    xxx_dev.cdev.owner=THIS_MODULE;
    /*获取字符设备号*/
    if(xxx_ major){
        register_chrdev_gion(xxx_dev_no,1,DEV_NAME);
    }
    else{
        alloc_chrdev_region(&xxx_dev_no,0,1,DEV_NAME);
    }
    ret=cdev_add(&xxx_dev.cede,xxx_dev_no,1); //注册设备
}

/*设备驱动模块卸载函数*/
static void exit xxx_exit(void)
{
    unregister_chrdev_region(xxx_ dev_no,1);
    /*释放占用的设备号*/
    cdev_del(&xxx_dev.cdev); /* 注销设备* 7
    ```
    return 0;
}

字符设备驱动程序read(),write(),ioctl()函数模板

/*读设备函数*/
static ssize_t xxx_ read (struct file *fi1p, char __user *buf,size_ t count, loff_ t *f_pos)
{
    copy_to_user (buf,...,...);
    ...
    return 0;

}

/*写设备函数*/
static ssize_t led_write(struct file *filp,const char__user *buf,size_t count,1off t*f_pos)
{
    ...
    copy_from_user(..., buf, ... );
    return 0;
}

/*I/O操作函数*/
int xxx_ioctl(struct inode *inode, struct file *filp,unsigned int cmd,unsigned long arg)
{
    ...
    switch(cmd){
        case xxx_CDM1:
        ...
        break;
        case xxx_CMD2:
        ...
        break;
        default:
        /*不能支持的命令*/
            return -ENOTTY;
    }
    return 0;
}

字符设备驱动程序file_operations结构体赋值操作模板

接下来再来看看教材中“GPIO设备驱动程序实例——LED驱动程序”

无操作系统时的LED驱动

#define reg_gpio_ctrl (*(volatile int *)(ToVirtual(GPIO_REG_CTRL)))
#define reg_gpio_data (*(volatile int *)(ToVirtual(GPIO_REG_DATA)))

/* 初始化 LED */
void LightInit(void)
{
    reg_gpio_ctrl |= (1 << n); /* 设置 GPIO 为输出 */
}

void Light0n(void) /*点亮LED*/
{
    reg_gpio_data |= (1 << n); /* 在 GPIO 上输出高电平 */
}

/* 熄灭 LED */
void Lightoff(void)
{
    reg_gpio_data &= ~(1 << n); /* 在 GPIO 上输出低电平 */
}

led_switch()函数

void led_switch(u8 sta)
{
    u32 val =0;
    if(sta == LEDON) {
        val = readl(GPIO1_DR);    /*读取该寄存器值*/
        val &=~(1<<3);    /*对LED对应引脚值设置为0*/
        writel(val, GPIO1_DR);    /*打开LED*/
    }
    else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val|=(1<<3);    /*对LED对应引脚值设置为1*/
        writel(val, GPIO1_DR);    /*关闭LED*/
    }
}

led_open()函数

static int led_open(struct inode *inode, struct file *filp) 
{
    filp->private_data = &dtsled; /* 设置私有数据 */ 
    return 0; 
}

led_read()函数

static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt,loff_t *offt)
{
    return 0;
}

led_write()函数

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt,loff_t *offt) 
{ 
    int retvalue; 
    unsigned char databuf[1]; 
    unsigned char ledstat; 
    retvalue = copy_from_user(databuf, buf, cnt); 
    if(retvalue < 0) { 
        printk("kernel write failed!\r\n"); 
        return -EFAULT; 
    }
    ledstat = databuf[0]; /* 获取状态值 */ 
    if(ledstat == LEDON) { 
        led_switch(LEDON); /* 打开LED灯 */ 
    } else if(ledstat == LEDOFF) { 
        led_switch(LEDOFF); /* 关闭LED灯 */ 
    }
    return 0; 
}

led_ioctl()函数

int led_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct light_dev *dev = filp->private_data;
    switch (cmd) {
        case LEDON:
            led_switch(LEDON); /* 打开LED灯 */
        break;
        case LEDOFF:
            led_switch(LEDOFF); /* 关闭LED灯 */
        break;
        default:
        /* 不能支持的命令 */
        return -ENOTTY;
    }
    return 0;
}

led_release()函数

static ssize_t led_release(struct inode *inode,struct file *filp)
{
    return 0;
}

led_init()函数

static int __init led_init(void)
{
    u32 val = 0;
    int ret;
    u32 regdata[14];
    const char *str;
    struct property *proper;
/************ 1. 获取设备树中的属性数据 ************/
    /* (1)获取设备节点:alphaled */
    dtsled.nd = of_find_node_by_path("/alphaled");
    if(dtsled.nd == NULL) {
        printk("alphaled node can not found!\r\n");
        return -EINVAL;
    } 
    else {
        printk("alphaled node has been found!\r\n");
    }

    /* (2)获取compatible属性内容 */
    proper = of_find_property(dtsled.nd,"compatible", NULL);
    if(proper == NULL) {
        printk("compatible property find 
        failed\r\n");
    }
    else {
        printk("compatible = %s\r\n",(char*)proper->value);
    }

    /* (3)获取status属性内容 */
    ret = of_property_read_string(dtsled.nd,"status", &str);
    if(ret < 0){
        printk("status read failed!\r\n");
    }
    else {
        printk("status = %s\r\n",str);
    }

    /* (4)获取reg属性内容 */
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if(ret < 0) {
        printk("reg property read failed!\r\n");
    }
    else {
        u8 i = 0;
        printk("reg data:\r\n");
        for(i = 0; i < 10; i++)
            printk("%#X ", regdata[i]);
        printk("\r\n");
    }

/*********** 2. 初始化LED ************/
#if 0  //这句话的含义有点没搞懂,是不是C++区别于C的某个语法呢?
/* (1)寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif

* (2)使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* (3)设置GPIO1_IO03的复用功能GPIO功
能,最后设置I/O属性。*/
writel(5, SW_MUX_GPIO1_IO03);
/* 寄存器SW_PAD_GPIO1_IO03设置IO属性
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* (4)设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* (5)默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);

/**************3.注册字符设备驱动 ***************/
/*(1)创建设备号 */
if (dtsled.major) { /* 静态设置设备号 */
    dtsled.devid = MKDEV(dtsled.major, 0);
    register_chrdev_region(dtsled.devid, 
    DTSLED_CNT, DTSLED_NAME);
}
else { /* 没有定义设备号,动态申请设置设备号 */
    alloc_chrdev_region(&dtsled.devid, 0, 
    DTSLED_CNT, DTSLED_NAME); /* 申请设备号 */
    dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */
    dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */
}
printk("dtsled major=%d,minor=%d\r\n",dtsled.major,dtsled.minor);

/* (2)初始化cdev */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);

/* (3)添加一个cdev */
cdev_add(&dtsled.cdev, dtsled.devid,DTSLED_CNT);

/*(4)创建类*/
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)){
    return PTR_ERR(dtsled.class);
}

/*(5)创建类设备*/
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL,DTSLED NAME);
if (IS_ERR(dtsled.device)){
    return PTR_ERR(dtsled.device);
    }
return 0;
}

驱动卸载函数led_exit()

static void __exit led_exit(void)
{ 
    /* 取消映射 */ 
    iounmap(IMX6U_CCM_CCGR1); 
    iounmap(SW_MUX_GPIO1_IO03); 
    iounmap(SW_PAD_GPIO1_IO03); 
    iounmap(GPIO1_DR); 
    iounmap(GPIO1_GDIR); 

    /* 注销字符设备驱动 */ 
    cdev_del(&dtsled.cdev);/* 删除cdev */ 
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/ 
    device_destroy(dtsled.class, dtsled.devid); 
    class_destroy(dtsled.class); 
} 

以上led_init()和led_exit函数使用模块化调用的方法

module_init(led_init); 
module_exit(led_exit);
led设备驱动实现read()、write()接口,应用层通过read()接口可获得led当前亮灭状态,通过write()接口可控制led亮灭。
设计驱动程序

流程图

导入必要的库

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

分配主设备号和设备名字,并定义开灯和关灯两种操作

#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

根据项目要求构造fileoperation结构体

/*文件操作集合的绑定,也就是设备操作函数*/
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_write,
	.open		= led_open,
	.read		= led_read,
	//PS:"ioctl" can both read and write,but I don't want to use it.
};

定义寄存器名字

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

在入口函数中用iommap将名字映射为地址

    /* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

在入口函数中利用定义的寄存器名做“使能”“配置input/output”和“控制高低电平”的任务。且由于我们使用“mknod /dev/led c 200 0”手动创建设备节点文件,因此省略了使用class_create和device_create函数智能创建设备号的过程

    int retvalue = 0;
    u32 val = 0;

    /* 2、使能 GPIO1 时钟 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); /* 清除以前的设置 */
    val |= (3 << 26); /* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、设置 GPIO1_IO03 的复用功能,将其复用为 GPIO1_IO03,最后设置 IO 属性。 */
    writel(5, SW_MUX_GPIO1_IO03);

      /*寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 
bit 16:0 HYS 关闭 
bit [15:14]: 00 默认下拉 
bit [13]: 0 kepper 功能
bit [12]: 1 pull/keeper 使能 
bit [11]: 0 关闭开路输出 
bit [7:6]: 10 速度 100Mhz 
bit [5:3]: 110 R0/6 驱动能力
 bit [0]: 0 低转换率 */
    writel(0x10B0, SW_PAD_GPIO1_IO03);

   /* 4、设置 GPIO1_IO03 为输出功能 */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3); /* 设置为输出 */
    writel(val, GPIO1_GDIR);

     /* 5、默认关闭 LED */
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

     /* 6、注册字符设备驱动 */
    retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(retvalue < 0){
        printk("register chrdev failed!\r\n");
        return -EIO;
    }

在出口函数中取消映射

/* 驱动出口函数 */
static void __exit led_exit(void)
{
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
}

编写led_write相关的函数

编写led_read相关的函数

整合上述内容,得到Linux字符设备的驱动架构图

设计应用程序
运行结果
led控制应用程序首先打印个人对应的“年级+专业+学号+姓名”信息,然后创建线程A与B,以及信号量A’与B’,线程A负责点亮led,线程B负责关闭led。A与B线程运行后分别等待信号量A’与B’到达;当线程A接收到信号量A’时点亮led,等待10秒后向线程B发送信号量B’,随即线程A又继续等待信号量A’;当线程B接收到信号量B’后关闭led,等待10秒后向线程A发送信号量A’,随即线程B又继续等待信号量B’。如此交替往复10次,应用程序打印输出“作业完成时间xxxx年xx月xx日”,然后安全退出。

流程图

根据项目要求构造fileoperation结构体

定义入口、出口函数,并设计程序创建class、设备并销毁它们

定义寄存器名字

在入口函数中用iommap将名字映射为地址

利用定义的寄存器名做“使能”“配置input/output”和“控制高低电平”的任务

编写led_write相关的函数

编写led_read相关的函数

整合上述内容,得到Linux字符设备的驱动架构图

设计应用程序
运行结果

块设备驱动程序与应用实例

其他问题

思考题

网络设备驱动程序与应用实例

其他问题

思考题

Linux移植与系统启动

其他问题

CRC是个什么东西?makecrc()函数是怎么做初始化的?

思考题

分析系统系统的主要作用是什么?Linux系统启动过程主要分为哪几个阶段?每个阶段完成哪些任务?
查找资料,分析u-boot的特点。u-boot如何实现对不同系列、不同型号处理器的启动支持?针对不同型号处理器,通过什么方式配置u-boot?针对不同拓展板,如何修改对应的参数。
针对你购买的开发版,试着裁剪内核,并编译启动测试。
参考开发版提供的根文件系统,制作并移植一种支持更高版本Linux内核的根文件系统。

Linux应用编程与实例

其他问题

思考题

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌入系统设计与应用是指在特定应用领域中,使用嵌入处理器和相关硬件资源,结合嵌入操作系统和软件开发工具,设计和开发满足特定需求的嵌入系统ARM Cortex-A8是一款高性能的嵌入处理器,广泛应用于手机、平板电脑、智能电视等嵌入设备中。它具有强大的计算能力和低功耗特性,能够提供快速、高效的数据处理和多任务处理能力。 Linux是一种开源的嵌入操作系统,提供了丰富的软件资源和开发工具,可以满足各种嵌入应用的需求。在使用ARM Cortex-A8和Linux进行嵌入系统设计和应用时,可以借助Linux的强大功能和丰富的软件生态系统,快速开发出满足特定需求的嵌入应用。 设计和开发嵌入系统时,首先需要选择合适的硬件平台和操作系统。选择ARM Cortex-A8作为处理器可以得到高性能和低功耗的优势,而选择Linux作为操作系统可以借助其丰富的软件资源和开发工具。 然后,根据具体的嵌入应用需求,对系统进行架构设计和软件模块划分。在嵌入系统设计中,需要考虑系统的实时性、功耗控制、硬件接口与外设的驱动、应用程序的开发等方面。 在应用开发阶段,可以使用C/C++等编程语言,结合相应的开发工具,编写应用程序和驱动程序。同时,可以借助Linux的丰富资源,如网络协议栈、文件系统、数据库等,快速实现系统的功能。 最后,在系统调试和测试阶段,可以使用调试工具和仿真平台进行系统性能测试和调试,以确保系统的稳定性和可靠性。 综上所述,嵌入系统设计与应用基于ARM Cortex-A8和Linux可以提供高性能、低功耗和丰富软件资源的优势,能够快速开发出满足特定需求的嵌入应用。 ### 回答2: 嵌入系统设计与应用是指将计算机系统嵌入到特定的电子设备中,以完成特定的功能。基于ARM Cortex-A8和Linux嵌入系统设计与应用是指利用ARM Cortex-A8处理器和Linux操作系统来设计和开发嵌入系统ARM Cortex-A8是一种高性能、低功耗的32位RISC处理器。它采用精简指令集架构,具有较高的运算能力和较低的能耗。Cortex-A8处理器广泛应用嵌入领域,可用于智能手机、平板电脑、汽车导航系统等各种嵌入设备。 Linux是一种开源的操作系统内核,具有广泛的硬件支持和强大的软件生态系统。在嵌入系统设计中,Linux提供了丰富的功能和驱动支持,能够提供稳定可靠的操作环境。同时,Linux还可以方便地进行定制和扩展,以满足各种应用需求。 在基于ARM Cortex-A8和Linux嵌入系统设计中,我们可以利用Linux提供的运行时库、工具链以及开发环境来进行系统开发。可以利用C/C++编程语言来进行应用程序的开发,使用Linux提供的设备驱动程序来进行硬件的控制和交互。同时,我们还可以利用Linux的网络支持和文件系统功能来实现网络连接和数据存储。 综上所述,基于ARM Cortex-A8和Linux嵌入系统设计与应用具有高性能、低功耗、可定制和可扩展等优势,可以适用于各种嵌入设备的开发和应用。它在智能手机、平板电脑、汽车导航系统等领域具有广泛的应用前景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值