基于项目驱动的嵌入式Linux应用设计开发学习笔记

基于项目驱动的嵌入式Linux应用设计开发学习笔记

库、文件、编译器使用

gcc
-o 文件名
-E 只做预处理 *.i
-S 只做预处理、汇编,得到汇编文件 *.s
-c 预处理、编译、汇编 得到目标文件.o *.o
-L 路径:指定库文件路径
-l 库名:指定库文件名字 把lib和库的扩展名省略

od 看二进制文件

程序包含多个源文件
分布编译
A.把所有.c编译成.o(-c) B.把所有.o文件链接起来,得到可执行程序

库:一个文件包含了很多编译好的目标模块(.o)
特点: 1.静态库:libXXX.a 2.动态库:libXXX.so
库的来源:1.系统自带 2.自己制作 3.网上下载

制作静态库 addvector.c mulvector.c 制作成libvector.a main.c调用libvector.a
1.写目标模块(.o)对应源程序(.c)
2.制作(.a) A.所有源程序编译成模块文件(.o) B. ar rcs *.a *.o 把这些目标文件打包成一个静态库文件
3.调用 在链接时,使用静态库的目标模块文件 gcc -L 路径:指定库文件路径
gcc -l 库名:指定库文件名字 把lib和库的扩展名省略
gcc -o main main.o -L . -l vector

动态库的制作 addvector.c mulvector.c 制作成libvector.so
gcc -fPIC -shared -o libvector.so addvector.c mulvector.c
main.c调用libvector.so gcc -o mainso main.c ./libvector.so

Makefile 程序员写的,不同程序内容不同 一个规则本质就是生成一个目标文件

程序检查错误
1.语法错误 用-c选项编译 2.链接错误 3.运行错误
只能用Makefile或makefile 其余不能用
1.Makefile文件由一系列规则组成
2.规则组成:A.目标和依赖关系(第一行)之间用冒号分隔,多个依赖用空格分隔
B.相应的linux指令(后续行)TAB建开头,执行由依赖文件生成目标文件
main:main.o addvector.o mulvector.o
gcc -o main main.o addvector.o mulvector.o
3.规则顺序 最终可执行程序所在规则,一般时Makefile的第一个规则
4.规则写到所有的源程序文件(.c)都依赖完,规则就写完了

make指令用法:
make 目标名(Makefile文件当中目标文件名) 目标名省略,默认生成第一条规则的目标文件
make内部的执行流程:
1.make目标文件:A.目标文件不存在,执行规则生成目标文件
B.依赖文件比目标文件新,重新生成目标文件

规则中,指令部分,除了写生成目标的指令之外,任何合法的linux指令,都可以写在规则中

makefile的伪目标: 没有依赖文件的目标
一般来说,定义伪目标,可以执行一些其他辅助指令 想要执行辅助指令时,make 伪目标
如果有巧合:目录下刚好有文件和伪目标重名,可以把伪目标定义为PHONY的依赖
make 伪目标 的时候,都会执行伪目标所对应指令
伪目标除非特殊指定,否则make的时候不会执行伪目标指令
make 伪目标; make

Makefile的变量
1.变量名:符合规则 2.变量赋值:=,+=,:= 变量名=值 3.变量的值都是字符型
4.变量的引用:$(变量名) 5.先给变量赋值,然后引用

交叉编译:
gcc:普通编译器
开发板tiny210,运行程序需要交叉编译器arm-linux-gcc

模式规则:用一个模式规则来解决一类相同或相似的规则
1.推导方法:根据推导方法,由依赖文件推导出它所对应的目标文件
目标文件(.o):%o:%c
main.o addvector.o mulvector.o:%.o:%.c
%:茎 %代表main,%.c变成main.c,依赖关系main.o:main.c
%代表main,%.c变成main.c,依赖关系main.o:main.c

推导方法中,目标文件(*.o)可以省略,只写%.o:%.c
%.o:%.c,推导过程:当前目录下所有.o文件依赖于.c文件

2.自动化变量:
$@:代表目标文件的逐个值 $<:代表依赖文件的逐个值

gdb调试器:编译后执行时发现有问题(非语法错误)可以用gdb调试
用法:1.在编译的时候,加上-g选项,否则不能用gdb调试 -c -g -o
2.调试的时候,调可执行程序,gdb 可执行程序
3.gdb 的指令:l(list):列出源码 b 行号:在某一行插入断点 r(run): 运行程序
p 变量名:查看变量值 s(step):单步运行,进入函数内部 n(next):单步运行,不进入
c(continue):从中断的地方继续往下执行 q(quit):退出gdb调试环境

文件时间编程

文件相关背景
1.文件的标识 用户角度:文件名+路径 系统角度:文件描述符,非负整数
2.都是字符类的文件/ASCII类型
3.有三个特殊的文件描述符:0–标准输入文件;1–标准输出文件;2–标准出错文件
重定向> 2> <
4.程序运行时,系统自动打开这三个标准文件
5.如果想要用程序处理文件,首先要打开一个已存在文件/创建一个新的文件,再操作

文件的打开或创建
int open(const char* pathname, int oflag, /*mode_t mode */)
参数:pathname–要打开的文件名(包含路径)常数字符指针,具体填入用“”括起来的字符串
oflag(open_flag)–打开标志,系统定义了一些宏(int),使用宏打开标志
O_RDONLY:以只读方式打开/创建文件
O_WRONLY:只写
O_RDWR:读写 这是第一组,有且只有一个
O_APPEND:打开/创建文件后,在文件最后追加内容
O_CREAT:用open创建文件,必须用这个宏 第二组,可以有多个,也可没有
用按位或运算“|”,把第一组和第二组链接
mode–moed_t用typedef重新定义的一个新类型,这个新类型也是用基本数据类型定义,只有在创建新文件
才使用,指明创建新文件权限,用三个八进制数确定新文件权限 r=4,w=2,x=1 0671
返回值:如果成功。返回已打开/创建文件的文件描述符,后续文件处理都要使用这个文件描述符,失败返回-1
值是系统根据实际情况返回,一般是可用文件描述符的最小值

文件关闭:int close(int fd)
功能:关闭文件,一般来说对文件的处理全部结束一会,调用close函数

往文件写入内容:
ssize_t write(int filedes, const void* buff, size_t nbytes)
参数:filedes–文件描述符 buff–要写入的内容存放在指针指向内存区域中
nbytes–用typedef定义,实际上就是整形,要写入的字节数
返回值:ssize_t,实际上就是整形,正常情况下返回值=nbytes,还有可能小于nbytes(物理空间不足)
错误返回-1

读文件:ssize_t read(int filedes, void* buff,size_t nbytes);
参数:文件描述符 存放读取内容的内存地址 要读取的字节数,一般这个值等于nbytes
返回值:nbytes 读到文件结尾,返回值是0

文件读/写的时候,隐含一个文件位置指针,read/write都从当前文件位置指针指示位置读、写
1.当问及刚打开或创建的时候,文件位置指针指向文件最开始位置
2.随着读、写的进行,读写完毕后文件位置指针会自动调整
人为调整文件位置指针位置
off_t lseel(int filesdes, off_t offset, int whence)
参数:文件描述符 相对于第三个参数的偏移量,是整数,+向下
whence–偏移基准:SEEK_SET:文件开始
SEEK_END:文件结束
SEEK_CUR:当前位置
返回值:正常返回新的文件位置指针位置,出错返回-1

linux的时间编程
时间类型:1.标准时间,格林威治时间 2.本地时间
表达方式:1.1970.1.1零点到现在经历秒数 2.用struct tm结构体,来存储具体年、月……
struct tm{
int tm_sec;
int tm_min;
int tm_hour;
}
获取时间函数 time_t time(time_t *tloc)1.time_t tt ; tt=time(NULL)
2.time_tt; time(&tt)

时间表达形式转换:
1.struct tm * localtime(time_t * timep) 功能:把日历时间转换为struct tm类型本地时间
2.time_t mktime(struct tm * )

颗粒度更细时间
int gettimeofday(struct timeval * tv, struct timezone, *tz)
struct timeval{
int tv_sec;
int tv_usec;//经历微秒数}
tz参数一般不用,设置NULL 把秒数、微秒时间

进程

时间函数 sleep(int sec):系统休眠sec秒
usleed(long usec):

1.进程的概念:一个具有一定独立功能的程序的一次运行活动 正在运行的程序
2.程序:一个文件,存储在系统的磁盘中,文件内容是CPU可以执行的二进制指令
A.程序是静态的 B.静态程序一旦运行起来,就变成一个进程
程序运行的要素:1.程序指令本身(存储在程序这个文件里面)
2.需要一些资源(内存,CPU的资源)
3.CPU资源:操作系统都是多任务,把CPU划分成很多时间片(几个ms),不一定平均分配CPU时间片
某个CPU的时间片分配、那个程序被CPU运行—操作系统有任务调度算法
4.操作系统需要对进程进行管理,标识用PCB(进程控制块)一种数据结构管理,和这个进程相关的一些属性
数据,都存储在这个进程的PCB,在PCB中有一个数据叫进程号PID,操作系统用进程号标识程序,
进程号是一个非负整数
5.进程怎么运行 A.双击/下达运行指令,程序从main开始,程序在连接的时候,都需要在main前链接一段
启动代码(crt0),main函数是由启动代码调用,启动代码也是一个进程,启动代码对应的进程
就称为自己写的这个程序的父进程,自己这个程序对应的进程就是子进程
B.进程想要启动,必须要被别的进程所调用(启用),那个进程叫父进程
C.在linux,所有程序逆序追踪,最终根部就是init的进程,进程ID是0,其父进程不属于操作系统一部分
叫做bootloader(引导启动程序)
系统开机—>运行bootloader—>init进程—>操作系统当中的其他进程
6.进程的状态:A.就绪态:进程已经具备执行的一切条件,等CPU的时间片
B.执行态:正在占用CPU时间片
C.等待态/阻塞态:进程正在等待某个事件的发生,直到该事件的发生,进程的阻塞才解除
7.进程是并发的:多个进程能够被CPU"同时"运行,多任务进程是异步的,进程各自独立,每个进程有着自己的
独立空间,互相不能访问

进程控制函数
1.获取进程号:pid_t getpid(void)
返回值:在哪个进程调用,就返回进程ID,类型pid_t,就是给int
2.获取父进程ID:pid_t getppid(void)
返回值:获取该进程父进程ID
3.进程创建函数fork
可以通过一个已存在的进程,创建出一个新进程,新进程称为子进程
pid_t fork(void)一次调用,返回两次,返回值可能有三种不同值
1.父进程当中,返回值是新创建的子进程ID
2.在子进程,返回0
3.出错返回-1
如果fork调用成功,父进程就会复制一个子进程(可执行指令要复制,定义的变量也要复制,打开的文件
的文件描述符也要复制,可执行指令部分+PCB部分),fork后,复制的子进程除了进程ID
之外,其他几乎和父进程一模一样,不同进程有自己独立的内存空间。

如果父进程在子进程结束之前退出,系统会自动把该子进程递归进init,也就是说该子进程的父进程变成init

进程终止:exit() 在_exit()函数基础上,做了一个包装,进程终止之前,缓冲区清空,内容写入文件
_exit() 进程马上终止,不做任何处理

进程等待:wait, waitpid
使用特点:调用wait/waitpid的进程可能会:
1.如果该进程所有子进程未结束,则该进程阻塞
2.如果一个子进程已经终止,正在等待/阻塞的父进程就解除阻塞,wait/waitpid立即返回
3.如果该进程没有子进程/所有的子进程都结束了,wait/waitpid立即返回,进程不阻塞

pid_t wait(int * status)
参数:status:用于保存子进程结束时的状态,如果不想保存这个状态,status设置为NULL
返回值:调用失败,返回-1,成功,为退出子进程ID

pid_t waitpid(pid_t pid, int * status, int options)
status和wait一样
pid:要等待的子进程ID,取值:
//pid<-1 等到进程组ID为Pid绝对值的任何子进程
pid=-1,等待任何子进程,作用相当于wait
//pid=0,等待进程组ID与目前进程相同的任何子进程
pid>0,等待进程ID为pid的子进程

options:取值有:0:表示不用任何选项
WNOHANG:如果等待的子进程还没结束,那么不予等待

缓冲区

例子:304 8-12
两个进程互斥访问标准输出
一个前台一个后台执行时,要带参数,&可执行程序main
后台执行:./main ffff &
前台执行:./main
使用一个初值为1的信号量,来实现前台进程的互斥

缓冲区问题,printf执行后,为什么能够看到结果
1.printf要把输出的内容放到输出缓冲区(一块内存区域)
2.要缓冲区的内容写入到标准输出文件(stdout)

内容必须要从缓冲写入到标准输出文件,才能看到内容
缓冲区的内容写入到标准输出文件中去
A.输出的内容最后有个回车符
B.调用fflush()函数 先把缓冲区内容写到文件,再清空 刷新函数,刷新缓冲区
C.进程结束的时候,

进程间通信

进程间内存空间是相互独立的,不同进程之间数据是不能互访的,有时候必须有一些数据交互,必须用
到进程间通信进程间通信是指不同进程之间传递数据,实现了不同进程之间的数据互访

整个系统当中,把内存按照区域,划分两大部分—运行OS内核程序,运行应用程序,从内核空间中申请一个
区域,不同的进程都可以访问这个区域,利用这个公共区域,实现不同进程的通信

通信方式
1.管道 2.信号量 3.共享内存 4.信号

管道通信 | 管道操作符
有名管道:没什么关系的两个进程通信
无名管道:在父子进程这样有关系的两个进程间通信
管道创建 int pipe(int fd[2])
创建一个无名管道,同时创建两个文件描述符,这两个文件描述符放在fd数组当中,其中fd[0]是管道的读端、fd[1]
是管道的写端
对管道的操作和对普通文件的操作没什么区别
对管道的写:write(int fd, void * buf, int nbytes)
对管道的读:read(int fd, void * buf, int nbytes)

无名管道的特点:
1.只能用在父子进程之间 2.对管道的操作和普通文件操作没有区别
创建子进程以后的管道状态
pipd(fd) fork()
父子进程使用无名管道通信的时候,必须管道创建在前,进程创建在后,保证子进程的内存空间当中,有操作
管道的文件描述符,子进程才能操作管道

父子进程之间使用管道通信的注意事项:
1.只有管道读端存在的时候,往管道写数据才有意义,此时会有错误题视,管道的读写端,都可以使用close函数进行关闭
close(fd[0]),close(fd[1])
2.往管道持续写入数据的时候,一旦管道满了,写入数据的进程就阻塞
3.持续读管道的进程,如果管道读空,该进程也阻塞
4要注意进程运行的顺序,一般来说先写后读,先运行写管道进程,然后运行读管道的进程
5.假设父进程写管道、子进程读管道,一般来说,创建完管道、创建完进程之后,主进程当中可以把读端关闭
子进程可以把写端关闭

共享内存:使用共享内存进行进程间通信的步骤
1.创建共享内存,使用shmget,也就是说从内核区域中获取一段共享内存区域
2.映射共享内存,把内核中创建的共享内存映射到具体的进程空间
shmat函数
3.对映射后的共享内存进行操作(读写)不带缓冲的I/O函数都可以用来读、写,函数中参数有对内存地址进行
操作的 printf("%s\n")
4.最后撤销共享内存映射,shmdt函数,然后把共享内存删除shmctl

int shmget(key_t key, int size, int shmflg)
功能:创建共享内存
参数:key-共享内存键值 共享内存大小 相当于open函数的权限部分(mode_t)三位八进制数
还必须有IPC_CREAT 例子:0666|IPC_CREAT
返回值:创建的共享内存的标识符,后续对共享内存操作都要用到标识符
键值:相当于文件的文件名,理解为共享内存的名字
标识符:相当于文件描述符
key的确定:
1.把key设置为IPC_PRIVATE,共享内存归该进程私有,如果shmget在fork之前调用,此时,子进程也可以使用
如果IPC_PRIVATE,共享内存只能在父子进程间使用
2.生成系统中唯一存在的key值,有一个函数ftok,能生成系统中唯一的不会重复的key值
key_t ftok(const char * filename, int proj_id)
系统已存在的文件名 id:不为零就行
返回值:就是唯一性的键值
ftok函数的作用就是利用文件的inode节点号来生成键值的,LINUX文件的inode节点号具有唯一性
3.系统中如果共享内存很少,也可以之间指定一个整数,作为共享内存的键值来用,不能保证唯一性

int open(const char * filename, int flag, /mode_t mode/)
文件名 返回值:文件描述符

void * shmat(int shmid,char * shemaddr, int shmflg)
shmid—共享内存的标识符,由shmget的返回值得到
shmaddr—将共享内存映射到指定地址,通常为0,表示系统自动决定,常用方式
—映射地址的标志,如果是0,表示可读可写
返回值—如果成功,返回共享内存映射到进程中的地址,后续,在该进程中对共享的操作,就用这个映射后的地址

int shmdt(char * shmaddr)
参数:映射的共享内存的地址 返回值:成功是0,出错-1

共享内存的控制函数shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
1.共享内存标识符 2.shmctl提供的命令,shmctl对共享的控制通过这些命令来完成
IPC_RMID:删除共享内存
IPC_STAT:得到共享内存的状态,状态复制到buf中
3.buf:共享内存管理的结构体

要删除一个共享内存:shmctl(shmid, IPC_RMID, 0)

main函数的参数 int main(int argc, char * argv[])保存命令行参数个数和内容
1.参数个数 2.保存命令行各参数的内容(字符串) ./保存在argv[0]中

主进程把程序参数传递来的信息存入共享内存

信号量

信号量:理解为一个变量,这个变量的值(整数)可以改变,通过控制信号量的值来控制进程的一些行为

停车场:车辆—进程,管理员—信号量,车位数—信号量的值,车辆进入—进程执行,车辆等待—进程阻塞
车辆出去—V操作
信号量作用,控制进程运行还是阻塞

1.进程A运行,首先对信号量做P操作(咨询管理员能否进入),做了P操作,信号量的值-1,如果信号量的值
是大于等于0的,进程A就能被运行,从停车场出来,做一个V操作,信号量的值+1

使用信号量控制进程运行,车位相当于临界资源,好多进程需要竞争使用的一些软件资源或硬件资源,
开发板中的串口
信号量初值,相当于临界资源数
对信号量:做P操作,相当于申请临界资源,信号量的值-1,如果信号量值>=0,执行否则阻塞,直到别的进程
做了V操作后,释放临界资源,信号量值+1,被阻塞进程解除阻塞
临界区:进程中访问临界资源的那一段程序代码

判断信号量的值,决定进程阻塞还是运行,是操作系统自动完成,不需要程序员完成

使用信号量的一般步骤
1.创建一个信号量,或系统中已存在的信号量,semget()函数
2.给信号量赋初值,决定了你的临界资源数,semctl()函数,整数
3.对信号量P/V操作,P是申请,V是释放,进程想使用临界资源,必须申请,semop()函数
4.信号量使用完毕后,要删除信号量,semctl()函数

int semget(key_t key, int sems, int semflg)
功能:创建/打开一个信号量集合
1.信号量的键值,信号量名
文件名——文件描述符,open
键值——信号量标识符,semget
键值——共享内存标识符,shmget
信号量起名,找到信号量,无名信号量,只能在有关系的进程中使用

唯一性,ftok函数来产生键值
key_t ftok(const char * filename, int proj_id)
1.系统中存在的文件名   2.不为零就行     key_t实际整数

2.nsems:信号量集中的包含的信号量数目,一般这个值是1
3.semflg:信号量标识,第一部分:是用8进制数表达的信号量集的权限0666
第二部分:新创建的信号量,需要或上0666 | IPC_CREAT
返回值:正常返回信号量的标识符,后续对这个信号量的操作,都要用到这个标识符,出错-1

int semctl(int semid, int semnum, int cmd);
int semctl(int semid, int semnum, int cmd,union semun arg);
功能:在semid标识的信号量中,对指定的某个信号量(semnum),执行某个操作/命令(cmd)
1.信号量标识符 2.信号量集中某个信号量的索引号,从0开始
3.要执行什么操作
4.根据cmd的不同,第四个arg参数有时候需要,有时候不需要
GETVAL:获取当前信号量的值
IPC_RMID:删除信号量
SETVAL:设置信号量的值,具体的值由arg的参数指定

union semnu{
int val; //SETVAL使用的值
struct semid_ds * buf;
unsigned short * array;
struct seminfo *__buf;
} //这个共用体没有定义,程序员需要自己定义,信号量初值设置
返回值,使用的cmd不一样,返回值不一样,失败-1
cmd=GETVAL返回值就是信号量当前值
cmd是其他,返回值是0

semop函数
int semop(int semid, struct sembuf * sops, unsigned nsops)
功能:对信号量P/V操作
1.信号量标识符 2.sembuf结构体数组,每个元素表示一个操作 P/V
3.指明sops数组中的元素个数,通常取值为1 P/V

struct sembuf{
unsigned short sem_num; //信号量集合中的某个信号量的索引号,从0开始
short sem_op; //如果是负值,调用semop函数之后,就把这个值加到信号量中去,相当于P操作
如果是正值,相当于V操作
short sem_flg; //信号量操作标志,取值一般有两个
A.IPC_NOWAIT,如果申请的临界资源不满足要求,进程也不阻塞
B.SEM_UNDO:程序结束时(不管是否正常结束),保证信号量的值会被重新设置为
semop调用之前的值
}

线程

1.控制线程的简称,是进程的一个实体
进程—系统可以有多个进程,fork创建出来,在进程内部,一个进程同时只能处理一个操作
随着计算机技术的发展,出现了线程,线程是属于进程
一般来说,一个进程中,有且只有一个线程(主线程),默认就有

多线程程序,可以在一个进程中再创建其他线程出来,构成多线程程序

线程实际上就是一个函数,多线程就是多个函数,主线程所在的函数就是main,和main同时运行,同时归
线程调度算法运行,分配CPU时间片
之前写的程序也有多个函数,除了main,还有其他函数,seminit,semp
这些函数执行和main串行的,这些函数的执行必须要通过main调用,不能和main同时执行

2.线程程序的编译
A.源程序当中必须包含pthread.h头文件
B.在gcc/arm-linux-gcc编译时,要使用-lptheread

3.也有线程号,来标识线程,pthread_t,实际上unsigned int

4.线程也需要创建出来,
int pthread_create(pthread_t * thread)
pthread_attr * attr
void * (* start_routine)(void *)
void * arg
1.thread 如果线程创建,就把线程号放在thread指针里
2.attr 创建线程的属性,设为NULL,表示使用线程默认属性
3.void * (*start_routine)(void *)指针,指向一个函数,函数有一个任意类型指针参数,返回值任意类型指针
4.arg 传递给start_routine的参数

pthread_create调用成功后,线程创建成功,调度启动该函数,如果有参数,就是arg。
线程创建成功,实际上就是调度启动由该函数的第三个参数和第四个参数决定的函数。

int fun(void):fun是个函数,函数无参,返回值是int类型指针
int fun(void * arg);fun是个函数,函数参数任意类型指针,返回值是int
int * fun(void):fun是个函数,函数无参,返回值是int类型指针
int (* fun)(void):fun是一个指针,是一个函数指针,他指向一个无参函数,同时返回值为int类型
int fun)(void *arg):fun是一个指针,是一个函数指针,他指向一个无参函数,同时返回值为int类型

pthread_self(void)线程内部调用这个函数,就返回进程ID
int pthread_join(pthread_t * thread, void **thread_return)
thread:线程ID号
thread_return:线程结束时返回值,一般为NULL
功能:在线程中调用pthread_join函数时,如果指定的线程thread还没有结束,该线程就阻塞,直到指定
的线程结束为止,类似进程wait函数

函数的并发执行控制,多线程程序的同步和互斥问题
1.互斥锁:实际上就是一个变量,对变量可以实现加锁或者解锁操作
如果线程对一个已经加锁的互斥锁执行加锁操作,该线程就阻塞,直到该互斥锁被别的线程进行解锁
互斥锁的数据类型:pthread_mutex_t
A.对互斥锁进行初始化;int pthread_mutex_init(pthread_mutex_t * restrict mutexattr_t * restrict attr)
参数:mutex,互斥锁指针 attr:互斥锁的属性,默认属性的使用NULL
B.加锁
int pthread_mutex_lock(pthead_mutex_t * mutex)
参数:mutex:要加锁的互斥锁
int pthread_mutex_trylock(pthead_mutex_t * mutex)
尝试对互斥锁加锁,如果互斥锁已经被加锁,线程不阻塞
C.解锁操作
int pthread_mutex_unlock(pthead_mutex_t * mutex)
D.互斥锁销毁
int pthread_mutex_destroy(pthead_mutex_t * mutex)

2.无名信号量
“1500”—>1500 atoi()
有名信号量(第五章),用于进程的同步和互斥
无名信号量用于线程的同步和互斥
1.创建信号量
int sem_init(sem_t * sem, int pshared, unsigned int value)
参数:sem:信号量标识符,和有名信号量不同,是sem_t类型
pshared:是否共享信号量,这个值一直设置为0
vaule:信号量的初始值
2.销毁
int sem_destory(sem_t * sem)
sem:信号量要销毁的信号量标识符
3.P操作 int sem_wait(sem_t * sem)
sem_wait对信号量做P操作的时候,信号量的值-1
4.V操作 int sem_post(sem_t *sem)
sem_post对信号量做V操作的时候,信号量的值+1
我们把这一类的无名信号量称为二进制信号量
5.获取信号量的值:int sem_getvalue(sem_t * sem)
函数的返回值就是sem信号量的当前值

线程/进程的同步和互斥问题:用信号量解决 **进程无控制锁

1.共享资源的互斥访问,一般设置信号量初值为1,在两个线程的临界区的前后都要做P/V操作
但是两个线程的执行顺序无法控制
如果是线程程序,也可以用互斥锁
2.要控制对共享资源访问的时间顺序:
A.设置一个初值为0的信号量
B.先执行的线程的临界区的后端对信号量做V操作,后执行的线程临界区的前端对信号量做P操作

另一种方式:
A.设置两个信号量,一个为1,另一个为0
B.先执行的线程P(S1)---临界区---V(S0)
   后执行的线程P(S0)---临界区---V(S1)

驱动

1.设备文件节点不能自动创建
2.主设备号要手动指定
3.不能操作具体的硬件设备

写驱动两大任务
1.驱动提供的对硬件的一些操作,所有操作都定义在struct fileoperations结构体
这个驱动要实现对LED的两种操作:
A.打开操作,实现结构体中的open函数
B.对LED的写操作,write(fd,&val,4),在应用程序中,如果写入1,四个LED都亮,如果写入0,让四个LED全灭
之前写的程序,控制LED灯,用的ioctl(fd,0|1,LED_NO)
2.驱动程序的框架结构

应用程序中的open、write、read、close、lseek、ioctl函数最终调用的是驱动中结构体中,open成员对应函数……
参数不是一一对应的,但是有数据传递的
write(fd,&val,4)val这个值实际上传递给buf

register_chrdev(111,“firstdrv”,&first_drv_fops)
第一个参数为0,调用成功后,就返回一个可用主设备号,后续可以用这个返回的主设备号

驱动中自动创建设备文件节点:
需要定义一个名字是class的结构体
struct class * class_create(struct module * owner, const char * name)
onwer=THIS_MODULE
name:自己取名

再调用device_create函数来创建设备文件节点
struct device * device_create(struct class * class, struct device * parent, dev_t devno , void* drvdata, void * devname)
class:class_create函数的返回值
parent:该设备的父设备,没有就设为NULL
devno:设备号,dev_t类型,整数,可以用一个宏来生成 MKDEV(int major, int minor)会生成dev_t类型的一个设备号
drvdata:回调函数输入的参数,没有设为NULL
devname:生成的设备文件节点名,文件会自动在/dev目录下生成。应用程序中

void device_destroy(struct class * class, struct dev_t devno)
void class_destroy()

GPJ2_0:GP:GERNEL I/O PORT
210芯片,通用I/O口大概有200多个
用每个I/O口都要配置相应的寄存器,把这些I/O口进行分组,编号A/B

寄存器地址:概念和内存地址概念是一样的,有的CPU,把寄存器和内存是统一线性编址的
210芯片是32V的,寄存器的宽度也是32位的

端口配置:
1.需要把GPJ2CON寄存器的[15:0]=0001000100010001,GPJ2_0~GPJ2_3配置为输出功能
程序写法:
GPJ2CON &=~(0XFFFF) 16个1->16个0
GPJ2CON=GPJ2CON | =(0X01<<0) | (0X01<<4) | (0X01<<8) | (0X0<<12)
00000001 0001000 000100000000 0001000000000000
2.让端口输出高、低电平,用到GPJ2DAT寄存器
GPJ2DAT &=~(0XF)
输出高电平:LED熄灭:GPJ2DAT | =(0xF);
输出低电平:LED点亮:GPJ2DAT | =(0xF);

硬件手册中寄存器的地址都是物理地址。在有操作系统支撑的程序中,不能直接用的。在裸机程序中是可以直接用的。操作系统
有一个功能MMU,内存管理单元,MMU其中的一个功能就是把实际的物理地址映射成虚拟地址来用。在OS支撑下,此程序中
看到的地址都是虚拟地址。

int a;
&a—取变量a的地址,这个地址就是虚拟地址。

在驱动中,需要把寄存器的物理地址映射成虚拟地址
void * ioremap(unsiged long phys_addr, unsigned long size)
phys_addr:实际的物理地址;
size:要映射的长度
返回值:返回虚拟地址
GPJ2CON:0XE0200280
GPJ2DAT:0XE0200284
映射:vaddr=ioremap(0XE0200280,24)
解除映射:iounmap(vaddr)

地址数据都定义为unsigned long 类型
volatile:关键字,意思是不要优化
int main()
{
int a,b,c; volatile----不要优化
a=10;
b=20;
c=b
10;
printf("%d,%d\n",c,b);
return 0;
}
gcc编译的时候,编译器会自动的把变量a删除,优化

应用程序和内核程序数据传递问题(驱动属于内核一部分)
copy_from_user 应用程序->内核
unsigned long copy_from_user(void * to, const void * from, unsigned long count)
to:目标地址
from:目标源地址
copy_to_user 内核->应用程序

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值