linux与linux驱动

1.可变形参

#include <stdarg.h>
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);
用法:
va_start(ap,fmt);
while(*fmt)
{
switch(fmt++)
{
case ‘d’:
i = va_arg(ap,long);
//或者
//i = va_arg(ap,int);
printf("d:%d ",i);
break;
case ‘f’:
f = va_arg(ap,double);
printf("f:%.2lf ",f);
break;
case ‘c’:
c = (char)va_arg(ap,int);
printf("c:%c ",c);
break;
case ‘s’:
s = va_arg(ap,char
);
printf("s:%s ",s);
break;
}
}
putchar(‘\n’);
va_end(ap);

2.目录操作

2.1基本函数
#include <sys/types.h>//目录操作头文件
#include <dirent.h>

struct dirent {
		ino_t d_ino; /* 节点号 */
		off_t d_off; /* 偏移量 */
		unsigned short d_reclen; /* 文件的长度*/
		unsigned char d_type; /* 文件的类型,目录==4,普通文件==8*/
		char d_name[256]; /* 文件名称 */
	};
/*创建目录之前,需要先执行 umask(0) 然后再调用 mkdir 函数。*/
umask(0);
mkdir(argv[1],S_IRWXO);

DIR *opendir(const char *name);//
int closedir(DIR *dirp);
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); //readdir 的可重入版本
void seekdir(DIR *dirp, long offset);//设置 readfir 函数的读取指针位置。
off_t telldir(DIR *dir); //返回目录流当前的读取位置
int mkdir(const char *pathname, mode_t mode); //创建目录,mode 是目录权限。
int rmdir(const char *pathname); //删除目录。只能删除空目录
int chmod(const char* path, mode_t mode); //mode 形如: 0777

2.2获取目录名
#include <unistd.h>
char *getcwd(char *buf, size_t size); //获取当前目录,相当于 pwd 命令,输入的路径除了最后一层
char *getwd(char *buf);
char *get_current_dir_name(void);
int chdir(const char *path); //修改当前目录,即切换目录,相当于 cd 命令

2.3剔除目录
#include <libgen.h>//剔除目录函数的头文件
char *basename(char *path); //只留下最后一层

2.4字符串拷贝
#include <string.h>//字符串拷贝
char *strdup(const char *s);
char *strndup(const char *s, size_t n);
char *strdupa(const char *s);
char *strndupa(const char *s, size_t n);

3.获取文件状态

#include <sys/stat.h>//获取文件状态函数头文件
#include <sys/types.h>
#include <unistd.h>

struct stat {
			dev_t st_dev; /* 文件所在设备的标识 */
			ino_t st_ino; /* 文件结点号 */
			mode_t st_mode; /* 文件保护模式 */
			nlink_t st_nlink; /* 硬连接数 */
			uid_t st_uid; /* 文件用户标识 */
			gid_t st_gid; /* 文件用户组标识 */
			dev_t st_rdev; /* 文件所表示的特殊设备文件的设备标识 */
			off_t st_size; /* 总大小,单位为字节*/
			blksize_t st_blksize; /* 文件系统的块大小 */
			blkcnt_t st_blocks; /* 分配给文件的块的数量, 512 字节为单元 */
			time_t st_atime; /* 最后访问时间 time_t 实际就一个长整型,保存的是秒数,可以使用 ctime 函数转为字符
			串*/
			time_t st_mtime; /* 最后修改时间 */
			time_t st_ctime; /* 最后状态改变时间 */
		};

S_ISLNK(st_mode): 是否是一个连接.
S_ISREG()是否是一个常规文件.
S_ISDIR()是否是一个目录
S_ISCHR()是否是一个字符设备.
S_ISBLK()是否是一个块设备
S_ISFIFO()是否是一个 FIFO 文件.
S_ISSOCK()是否是一个 SOCKET 文件.
int stat(const char *path, struct stat *buf); //获取文件的状态保存在 buf 里。(可以直接传入文件路径)
int fstat(int fd, struct stat *buf); //获取文件的状态保存在 buf 里(针对已经打开的文件进行操作)
int lstat(const char *path, struct stat *buf);//获取文件的状态保存在 buf 里。(不区分链接文件)

4.基于文件指针的文件操作函数

#include <stdio.h> //基于文件指针的函数

FILE *fopen(const char *path,const char *mode);
FILE *fdopen(int filds,const char *mode);
FILE *freopen(const char *path,const char *mode, FILE *stream);
int fclose(FILE *stream);
int fgetc(FILE *fp);//文件中获得一个字符
int fputc(int ch,FILE *fp);//向文件写入一个字符
char *fgets(char *str,int n,FILE *fp);
int fputs(char *str,FILE *fp);
int fprintf(FILE *fp,char *format,arg_list);//向文件写格式化数据
int fscanf (FILE *fp,char *format,arg_list);//格式化读数据
int fread(void *buffer,unsigned sife,unsigned count,FILE *fp)
int fwrite(void *buffer,unsigned sife,unsigned count,FILE *fp)
int getw(FILE *fp) //以二进制形式到文件读取一个整数
int putw(int n,FILE *fp) //以二进制形式向文件存一个整数
int feof(FILE *fp);//用来侦测读写文件指针是否读取到了文件尾。
long ftell(FILE *fp);//文件指针当前位置
void rewind(FILE *fp);//回到文件指针头
int fseek(FILE *fp,long offset,int base);//定位 参数3:SEEK_SET SEEK_CUR SEEK_END 
FILE *tmpfile(void) //以二进制更新模式(wb+)创建临时文件。被创建的临时文件会在流关闭的时候或者在程序终止的时候自动删除。
int fsetpos(FILE *stream, fpos_t *pos);
int fgetpos(FILE *stream, fpos_t *pos);
int rename(const char *oldpath, const char *newpath);//重命名

5.基于文件描述符的文件操作函数

#include <fcntl.h>//文件描述符的头文件
#include <unistd.h>
int open(const char *pathname, int flags); //文件名 打开方式
int open(const char *pathname, int flags, mode_t mode);//文件名 打开方式 权限
int close(int fd); //关闭文件, fd 表示文件描述符
size_t read(int fd, void *buf, size_t count);//文件描述词 缓冲区 长度
size_t write(int fd, const void *buf, size_t count);
#include <stdio.h>
FILE * fdopen(int filedes, const char *opentype); //文件指针与文件描述符的转换
int fileno(FILE *stream);//把文件流指针转换成文件描述符用 fileno 函数
int remove(const char *pathname);//删除文件或目录
int unlink(const char *filename) //删除文件

6.形参解析函数

#include <unistd.h>//形参解析函数头文件
int getopt(int argc, char * const argv[], const char *optstring);//只支持短形参-r等等
extern char *optarg; //头文件里面的全局变量,可直接调用
extern int optind, opterr, optopt;
第三个参数传入想要定义到参数类型,如-a,-b,-c等,填入形式"abc",或"a🅱️c:",加:可以在这个参数后面传入字符串

#include <getopt.h>
int getopt_long(int argc, char * const argv[], const char *optstring,const struct option *longopts, int *longindex);//兼容短形参-r和长形参–r

6.1使用
	 struct option ionion[]=
  {
     {"yjw",1,NULL,'y'},
      {"sll",1,NULL,'s'},
      {"lzj",0,NULL,'l'},//0代表不要参数带字符串
      {0,0,0,0},
  };
  int res=0;
  int longindex=0;
  while(1)
  {
      res = getopt_long(argc,argv,"a:b:c:d:",ionion,&longindex);
      if(res==-1)break;
      switch(res)
     {
         case 'a':
              printf("-a:%s\n",optarg);
              break;
          case 'b':
              printf("-b:%s\n",optarg);
              break;
          case 'c':
              printf("-c:%s\n",optarg);
              break;
          case 'd':
              printf("-d:%s\n",optarg);
              break;
          case 'y':
             printf("-yjw:%s offset:%d\n",optarg,longindex);
              break;
          case 's':
              printf("-sll:%s offset:%d\n",optarg,longindex);
              break;
          case 'l':
              printf("-lzj:%s offset:%d\n",optarg,longindex);
              //printf("-lzj\n");
              break;
      }
  }

7.打开动态库的函数

#include <dlfcn.h>//打开动态库的函数头文件

void *dlopen(const char *filename, int flag);//动态库的路径加名字,
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);//dlopen的返回值,函数名或者变量名
int dlclose(void *handle);
编译时加-ldl

7.1使用:
	/*核心:打开动态库,*/
  void *handle;
  int (*add)(int,int,char);//保存获取到的函数的函数指针
  int *data;
  handle = dlopen("sum/libsum.so",RTLD_LAZY);
  if(handle==NULL)
  {
      printf("找库失败\n");
      exit(0);
  }
  //调用,函数"sum"或者动态库中的全局变量"data"
  add = dlsym(handle,"sum");
  data = dlsym(handle,"ddd");
  if(add==NULL)
  {
      printf("找函数失败\n");
      exit(0);
  }

  int a = atoi(argv[1]);
  int b = atoi(argv[2]);
  char c = argv[3][0];
  int z = add(a,b,c);
  printf("ddd:%d\n",*data);
  printf("运算结果:%d\n",z);
  //关闭
  dlclose(handle);

8获取环境变量的函数

#include <stdlib.h> //获取环境变量的函数头文件
/全部只在当前进程里有效/
char *getenv(const char *name); //获取环境变量的值 常用
int clearenv(void); //清除环境变量的值
int setenv(const char *name, const char *value, int overwrite);// 用来改变或增加环境变量的内容。
int putenv(char string); //用来改变或者增加环境变量的内容
int unsetenv(const char
name) //删除 name 环境变量的定义,即使不存在也不会出错

8.1使用:
	char *env_p;
	env_p=getenv("PATH");
	if(env_p==NULL)exit(0);
	printf("PATH=%s\n",env_p);

9.automake自动生成Makefile工具简单使用:

1.写好代码后顶层目录下敲命令:autoscan
2.把生成的configure.scan改为.ac后缀
3.修改.ac后缀文件的内容
	:AC_INIT([yjw], [1.2.3], [2453013768@qq.com])  //三个方括号内改为自己想要的东西,分别代表:生成的可执行名、版本号、联系地址
	 AC_CONFIG_SRCDIR([main/main.c])               //自动识别包含main函数的主文件,但是又可能识别失败,需要自行修改
	 AC_PROG_CC   								   //默认gcc编译器,修改可在变量后直接跟小括号,内填编译器名
	 AC_PROG_RANLIB								   //当需要生成静态库时,要加上此全局变量(动态库宏:   AC_PROG_LIBTOO)
	 AM_INIT_AUTOMAKE(yjw,1.2.3)				   //都要加上这句话,两个参数和上面的要一致
	 AC_OUTPUT									   //该变量指定在哪生成Makefile文件,如:
												   //	AC_OUTPUT(Makefile main/Makefile sub/Makefile sum/Makefile)
PS:(如果是动态库,除了改变.ac里面的静态库宏为动态宏以外,还要把所有.am中的.a后缀改为.la,并且所有宏也要改为对应的宏。然后在执行下面步骤之前先执行: libtoolize -f -c)
4.执行 aclocal 命令生成 aclocal.m4 文件 
5.使用 autoconf 工具生成 configure 文件
6.使用 autoheader 生成 config.h.in 文件 	       //4、5、6是分别打上命令回车即可
7.在AC_OUT指定的几个地方分别创建Makefile.am文件,打开分别编写各自的内容(用到的变量和方法参考万邦automake教程,可以放在第8步之前的任何位置)
8.执行automake --add-missing
9.执行./configure --prefix=路径
10.执行make 进行编译
11.执行make install 进行安装

10.shell

10.1示例

11.进程

11.1函数
fg :后台进程搬移到前台
bg :后台暂停进程开始
Ctrl+z :把正在运行的进程暂停并放在后台
nice
renice
ps
jobs :查看后台的进程
top :动态查看进程
& :加在命令后,把该命令放在后台执行
kill
fork :
getpid :得到PID号
exit() 和_exit() 第一个退出时会清除IO缓冲区数据,输出到标准输出,再调用exit的系统调用退出程序。第二个直接调用系统调用,不会清除IO缓冲区。如:把printf(“111111”);打印函数后分别调用exit和_exit结果不同,exit有数据打印在屏幕.(不能加\n,\n为行缓冲的标志)
int pause(void); //暂停程序, 暂停进程在收到信号后可以自行继续运行

捕获信号函数
include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);//形参:创建一个符合函数指针类型的函数,当作第二个参数,第一个参数填入linux系统信号。
Kill -l 可以查看系统全部信号及其编号

发送信号的方法:
kill [-s signal|-p] [–] pid…
kill -l [signal]

kill -s 2 1234 //给PID为1234的进程发送2这个信号
kill -2 1234 //给PID为1234的进程发送2这个信号

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig); //通过C语言函数接口给指定的进程发送信号

系统调用类:
System() 传入可执行函数绝对路径,不会对调用者产生影响。

捕获流管道:FILE *popen(const char *command, const char *type); //流管道,参数:命令,打开方式“r”“w”
示例 popen(“ls *.mp3”,“r”);

exec族:
系统调用,会覆盖调用者进程的函数空间,调用者代码失效
#include <unistd.h>
extern char **environ;
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[]);
使用方法:path表示传入绝对路径,file表示传入可执行文件名(必须存在在PATH的值下)
//char const argv_cmd[] = {“ls”,”-l”,”.c”}
//execl(“/bin/ls”,“ls”,“-l”,NULL);
//execlp(“ls”,“ls”,“-l”,NULL);
//execv(“/bin/ls”,argv_cmd);
//execl(“/work/a.out”,“a.out”,“123”,“456”,NULL);
//execvp(“ls”,argv_cmd);

11.2进程间通信
管道:
有阻塞功能,管道写端没数据发送过来时会一直等待(不分命名和无名管道),适合一对一通信
命名管道:任何进程都可以进行通信
命令行:mkfifo xxx:创建fifo文件(也就是命名管道,有实体,可以在不同进程间通信)
函数: int mkfifo(const char *pathname,mode_t mode);
无名管道:只能亲戚关系的进程间通信方式
int pipe(int pipefd[2]); #创建无名管道,无名管道就是没有名字也就是没有实体,只能在有亲戚关系的进程间通信 。pipefd[2];//0是读端,1是写端
示例:

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

int main(int argc,char **argv)
{
pid_t pid;
char buff[100];
int cnt;
int status;
int pipefd[2]; //[0]表示读 [1]写
//1.创建无名管道
pipe(pipefd);
//2.创建子进程
pid=fork();
if(pid==0)
{
printf(“子进程的PID:%d\n”,getpid());
//从管道的读端读取数据
cnt=read(pipefd[0],buff,100);
printf(“读取的字节:%d,%s\n”,cnt,buff);
}
else if(pid>0)
{
printf(“父进程的PID:%d\n”,getpid());
sleep(3);
//向管道的写端写数据
write(pipefd[1],“12345”,6);
//等待子进程结束
wait(&status);
}
else
{
printf(“进程创建失败!\n”);
}
return 0;
}

消息队列:
适合一对多通信,可以一个用来发消息,多个用来读消息(ipcs -q来查看当前所有的消息队列情况)。

写队列:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//int msgget(key_t key, int msgflg);创建和访问一个消息队列。创建标志的可选值为 IPC_CREAT
//int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsz, int msgflg);将消息添加到队列中
//ssize_t msgrcv(int msqid, struct msgbuf * msgp, size_t msgsz, long msgtyp, int msgflg);
//从一个消息队列中获取信息。msgflg:有 3 种选项:0:当消息队列满时,msgsnd 将会阻塞,直到消息能写进消息队列
//int msgctl(int msqid, int cmd, struct msqid_ds * buf);控制函数,常用来删除消息队列。

//消息队列:生成键值的文件不能和共享内存生成键值的文件相同,会产生错误.和创建共享内存大致相同,msqid一般为msgget的返回值 ,msgsed的第二个参数格式是一个结构体类型,定义方式如下
#define BUFF_SIZE 100
struct msgbuf
{
long mtype; //消息类型必须是 > 0,因为选择读取参数类型为0时,默认读取队列全部消息
char mtext[BUFF_SIZE]; //数据大小
// //可以往后重复前两个格式
//
};
int main(int argc,char **argv)
{
if(argc!=4){ printf(“格式:./a.out <消息内容> <消息类型-整数>\n”);exit(0);}
key_t key = ftok(argv[1],200);
if(key == -1) {printf(“生成key失败!\n”); exit(0);}
int msq_id = msgget(key,IPC_CREAT|0666);
if(msq_id == -1) {printf(“消息队列创建失败!\n”); exit(0);}
struct msgbuf msg_buff;
msg_buff.mtype = atoi(argv[3]);
strncpy(msg_buff.mtext,argv[2],BUFF_SIZE);
printf(“key:%d msqid:%d\n”,key,msq_id);
printf(“send:%d\n”,msgsnd(msq_id,&msg_buff,sizeof(struct msgbuf),0));
return 0;
}

读队列:

//读消息队列
#define BUFF_SIZE 100
struct msgbuf
{
long mtype; //消息类型必须是 > 0,因为选择读取参数类型为0时,默认读取队列全部消息
char mtext[BUFF_SIZE]; //数据大小
// //可以往后重复前两个格式
//
};
int main(int argc,char **argv)
{
if(argc!=3){ printf(“格式:./a.out <消息类型-整数>\n”);exit(0);}
key_t key = ftok(argv[1],200);
if(key == -1) {printf(“生成key失败!\n”); exit(0);}
int msq_id = msgget(key,IPC_CREAT|0666);
if(msq_id == -1) {printf(“消息队列创建失败!\n”); exit(0);}
struct msgbuf msg_buff;
printf(“key:%d msqid:%d\n”,key,msq_id);
while(1)
{
printf(“rcv:%d\n”,msgrcv(msq_id,&msg_buff,sizeof(struct msgbuf),atoi(argv[2]),0));//0表示读取不到就阻塞
printf(“type:%d data:%s\n”,msg_buff.mtype,msg_buff.mtext);
}
//msgctl(msq_id,IPC_RMID,NULL);//删除消息队列,参数2通常为 IPC_RMID 表示删除消息队列。
return 0;
}

内存映射:
#include <sys/mman.h>

//进行内存映射,将文件直接映射到内存条上,对数据进行地址操作。可选映射模式为共享,其他文件也可访问
//适合大文件复制
//void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
//addr:选择映射在内存上的位置,填NULL系统自行分配;lenth选择分配大小;prot表示映射区域的权限,四种:可执行、//读、写、不能存取(PROT_EXEC、PROT_READ、PROT_WRITE、PROT_NONE);flags表示映射区域的各种特性(一般填MAP_SHARE//D-对映射区域的写入数>据会复制回文件内,而且允许其他映射该文件的进程共享。);fd:文件描述符;offset:对于文件//的偏移量,一般0,返回值:分配地址的首地址.
//通过内存映射进行进程通信,多个进程可以同时映射同一个文件到内存空间,只要一个进程对文件进行了修改,其他进程都可以得到修改的数据。关闭对应的文件描述词时不会解除映射.分配的空间大小最多和原文件相同大小,可用int ftruncate(int fd, off_t length);修改文件大小
int main(int argc,char **argv)
{
if(argc!=2)
{
printf(“格式:./a.out \n”);//运行前必须有文件,因为打开方式不带创建
exit(0);
}
int fd;
char *mem_p;
struct stat st_s;
fd=open(argv[1],O_RDWR);
if(fd<0)
{
printf(“打开文件失败!\n”);
exit(0);
}
stat(argv[1],&st_s);
mem_p=mmap(NULL,st_s.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
printf(“size:%d\n”,st_s.st_size);
if(mem_p==NULL)
{
printf(“内存映射失败!\n”);
exit(0);
}
//直接对地址进行操作,也能改变文件内容
printf(“原数据:%s\n”,mem_p);
memset(mem_p,‘\0’,st_s.st_size);
memcpy(mem_p,“0123488\n”,8); //小于原文件内容时,剩下的空间变为’\0’,打开为乱码
munmap(mem_p,st_s.st_size);//取消映射,可不取消
close(fd);
return 0;
}

共享内存:
适合一对多来通信,多个进程可指向同个共享内存
写内存:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
//key_t ftok(const char *pathname, int proj_id);创建一个关键字,传入一个任意文件和数字,生成唯一键值
//int shmget(key_t key, size_t size, int shmflg);用于创建或打开一共享内存段,参数3:IPC_CREAT,段不存在则创建
//void *shmat(int shmid, const void *shmaddr, int shmflg);共享内存段映射到进程空间的某一地址,地址通常填NULL
//int shmdt(const void *shmaddr);将共享内存段与进程空间分离, 与 shmat 函数相反.并不会销毁空间
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);共享内存的控制函数,可以用来删除共享内存段。
//共享内存可以多个进程指向一个内存,只要设置其共享内存标识符一致即可。共享内存段的标识通常应该是 shmget 的
//成功返回值。这里传入文件只是用来生成键值,并没有对文件产生影响
//root权限运行!!!!!!!!!!!!!!!!
#define MEM_LEN 1024
int main(int argc,char ** argv)
{
if(argc!=3){printf(“格式:./a.out <通信内容>\n”);exit(0);}
key_t key = ftok(argv[1],200);
if(key == -1 ) { printf(“生成key失败!\n”); exit(0);}
int shm_id = shmget(key,MEM_LEN,IPC_CREAT);
if(shm_id < 0) { printf(“生成内存标识符失败!\n”); exit(0); }
char *mem_addr = shmat(shm_id,NULL,0);
if(mem_addr==NULL) {printf(“共享内存映射失败!\n”); exit(0); }
printf(“key:%d id:%d addr:%x\n”,key,shm_id,mem_addr);
memset(mem_addr,0,MEM_LEN);//共享内存映射成功,来写数据进行传输
strncpy(mem_addr,“你是最胖的!”,MEM_LEN);
strcat(mem_addr,argv[2]);
shmdt(mem_addr);
return 0;
}

读内存:
#define MEM_LEN 1024
int main(int argc,char ** argv)
{
if(argc!=2)
{
printf(“格式:./a.out \n”);
exit(0);
}
key_t key;
int shm_id;
char *mem_addr;
key = ftok(argv[1],200);
if(key == -1) { printf(“生成key失败!\n”); exit(0);}
shm_id = shmget(key,MEM_LEN,IPC_CREAT);
if(shm_id < 0) { printf(“生成内存标识符失败!\n”); exit(0); }
mem_addr = shmat(shm_id,NULL,0);//进行内存映射,指向有数据的共享内存
if(mem_addr==NULL) { printf(“共享内存映射失败!\n”); exit(0); }
printf(“key:%d id:%d addr:%x\n”,key,shm_id,mem_addr);
printf(“读取的数据:%s\n”,mem_addr);
// /5. 分离内存/
// shmdt(mem_addr);
// /6. 删除内存段/
// shmctl(shm_id,IPC_RMID,NULL);
return 0;
}

11.3:利用内存映射多进程拷贝文件
//fork多个进程来拷贝文件,将目标文件映射到内存中。加快拷贝速度
int main(int argc,char **argv)
{
if(argc!=4)
{
printf(“格式:./a.out <src_file> <obj_file> <进程个数> \n”);
exit(0);
}
int s_fd,o_fd;
int course_num,i;//进程个数
struct stat s_info;
pid_t pid;
char obj_mem_addr;//目标文件的内存映射首地址
//打开源文件和创建目标文件
s_fd = open(argv[1],O_RDWR);
if(s_fd<0) {printf(“打开源文件失败!\n”);exit(0);}
o_fd = open(argv[2],O_RDWR | O_CREAT,S_IWUSR|S_IRWXG|S_IRWXO);
if(o_fd<0) {printf(“打开或者创建目标文件失败!\n”); exit(0);}
//因为映射目标文件到内存时,目标文件或许为空或者大小比源文件小而造成不能全部拷贝成功,所以要改变文件大小
stat(argv[1],&s_info);//获取源文件大小
printf(“s_file_size:%d\n”,s_info.st_size);
ftruncate(o_fd,s_info.st_size);//改变文件大小
obj_mem_addr = mmap(NULL,s_info.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,o_fd,0);
if(obj_mem_addr == NULL) {printf(“目标文件内存映射失败!\n”);exit(0);}
//开始创建进程
course_num = atoi(argv[3]);
int sun_cpy_byte = s_info.st_size / course_num ;//计算每个子进程拷贝字节数
int far_cpy_byte = s_info.st_size % course_num ;//计算父进程拷贝字节数
for(i=0;i<course_num;i++)
{
pid = fork();
if(pid==0)break; //创建一个子进程,子进程就退出.保证父进程在创建进程
if(pid<0)
{
while(1)
if(wait()<0)break;
printf(“fork失败!\n”);
exit(0);
}
//sleep(1);
}
if(i<course_num)//i小于进程个数时的进程是子进程
{
read(s_fd,obj_mem_addr+i
sun_cpy_byte,sun_cpy_byte);//往内存空间读取数据,目标文件内容也会改变
printf(“子进程%d 拷贝成功%d Byte!\n”,i,sun_cpy_byte);
}
else
{
read(s_fd,obj_mem_addr+i*sun_cpy_byte,far_cpy_byte);
printf(“父进程 拷贝成功%d Byte!\n”,i,far_cpy_byte);
while(1)
if(wait()<0)break;
printf(“文件拷贝完成!\n”);
}
}

12.复制文件描述符状态(重定向的底层函数(dup))

#include <unistd.h>
int dup(int oldfd); //复制当前文件描述符,通过返回值返回
int dup2(int oldfd, int newfd);//将一个文件描述符状态复制给另一个描述符,
如 dup2(pipefd[1],1); //将无名管道写端描述符状态给标准输出,那么该进程的所有的标准输出都输出到无名管道写端,可在读端读出来。(想要恢复标准输出,可用dup()提前备份)
#define _GNU_SOURCE
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);

dup2的应用示例:
示例1:
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
if(argc!=2)
{
printf(“参数格式:./app <新文件名称>\n”);
return 0;
}
int fd;
fd=open(argv[1],O_RDWR|O_CREAT,S_IWUSR|S_IRUSR);
if(fd<0)
{
printf(“创建文件失败.\n”);
exit(0);
}
//int fd1=dup(1); //复制一个新的文件描述符
dup2(fd,1); //将fd的文件状态复制给1(标准输出)
printf(“123\n”);
printf(“456\n”);
close(fd);
return 0;
}

示例2:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
char buff[1024];
int main(int argc,char **argv)
{
pid_t pid;
int cnt;
int argfd[2];
pipe(argfd); //[0]读 [1]写
pid=fork();
if(pid==0)
{
dup2(argfd[1],1); //1表示输出文件描述符
//printf(“12345-我是子进程打印的数据!\n”);
execlp(“ls”,“ls”,“-l”,NULL);
}
else if(pid>0)
{
cnt=read(argfd[0],buff,1024);
buff[cnt]=‘\0’;
printf(“buff=%s\n”,buff);
wait(NULL);
}
return 0;
}

13.线程

13.1:线程基础
#include <pthread.h>

int pthread_create
(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg); //创建函数,参数1可得到线程id,类型为unsigned long; 2为设置属性,一般为NULL(有两种状态:默认结合状态,还有分离状态);3为线程函数指针,填函数地址;4为3的形参,用不到填NULL

Int pthread_cancel(pthread_t thread); 杀死同一进程下的一个线程

void pthread_exit(void *retval); 线程退出函数,可填形参作为为返回值,如“abc”。可char *p,用&p接收返回值

int pthread_join(pthread_t thread, void **retval); 线程等待函数,带阻塞功能,等待不来线程结束信号,会一直卡死。参数2可接收形参返回值(唯一一个函数)。接收方法如上。

int pthread_detach(pthread_t thread);
//线程分离函数:该函数将默认的结合状态(joinable)变为分离状态(detached),则该线程运行结束后会自动释放所有资源。用该函数想要还使线
//程无线循环,则主线程需要是死循环,和pthread_join相同这两个函数都是处理正常结束的线程的资源。当线程异常结束时,则用到的是另一函数组(下面两个):

void pthread_cleanup_push(void (*routine)(void *),void *arg); //注册清理函数,传入的函数用来处理一些善后工作
void pthread_cleanup_pop(int execute); //释放清理函数,形参填0和1两种,0不调用清理函数,1调用清理函数
//触发调用清理函数的方法有三种:1–execute==1;2–调用pthread_exit()函数;3–当被同进程下的另一线程杀死时:pthread_cancel(线程id)

综合示例:

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

pthread_t thread_id1;//定义保存线程标识符的变量,类型为unsigned long 用lu打印数值
pthread_t thread_id2;

void routine_1(void *arg)//清理函数,无返回值
{
//free(),close(),等等函数
printf(“线程1已经清除完毕\n”);
}

void *start_func1(void *arg)//创建线程子函数实体1
{
pthread_cleanup_push(routine_1,NULL);
printf(“线程1!\n”);
sleep(5);
//pthread_exit(“帅啊!”);//线程退出函数,相当于进程的exit()函数,返回值形参可以填一个地址,通过pthread_join接收
pthread_exit(NULL);//如果下面调用自动回收资源的函数(detach和cleanup组)不支持接收返回值
pthread_cleanup_pop(0);//填0本身不会触发调用清理函数,但是中间有pthread_exit()
}
void *start_func2(void *arg)//创建线程子函数实体2
{
while(1)
{
printf(“线程2!\n”);
sleep(1);
//pthread_cancel(thread_id1);//也会触发线程1的清理函数
}
}
int main(int argc,char **argv)
{
char *p;
if(pthread_create(&thread_id1,NULL,start_func1,NULL))
{
printf(“创建线程1失败!\n”);
exit(0);
}
//pthread_detach(thread_id1);
//线程分离函数该函数将默认的结合状态(joinable)变为分离状态(detached),则该线程运行结束后会自动释放所有资源。用该函数想要还使线
//程无线循环,则主线程需要是死循环,和pthread_join相同这两个函数都是处理正常结束的线程的资源。当线程异常结束时,则用到的是另一函
//数组:
//void pthread_cleanup_push(void (*routine)(void *),void *arg); //注册清理函数,传入的函数用来处理一些善后工作
//void pthread_cleanup_pop(int execute); //释放清理函数,形参填0和1两种,0不调用清理函数,1调用清理函数
//触发调用清理函数的方法有三种:1–execute==1;2–调用pthread_exit()函数;3–当被同进程下的另一线程杀死时:pthread_cancel(线程id)
if(pthread_create(&thread_id2,NULL,start_func2,NULL))
{
printf(“创建线程2失败!\n”);
exit(0);
}
//pthread_detach(thread_id2);//比较常用

//pthread_join(thread_id1,(void **)&p);//不常用,因为在线程多时,手动处理麻烦,一般设置为自动收回线程占用的资源
//printf("线程1返回值:%s\n",p);
//pthread_join(thread_id2,NULL);//等待线程结束,等待不到结果会阻塞,第二个参数用来接收返回地址,是一个二维指针,不接收为NULL

//除了调用join函数,这个阻塞对于那些线程的实现是必不可少的
while(1)
{

}
return 0;

13.2:线程通信机制(同步和互斥)

13.2.1:互斥锁
互斥锁:适用在多个线程之间中只能同时运行一个能被或者能影响别的线程的情况
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 初始化锁,参数2为状态,填NULL默认快速锁
使用初始化函数为动态初始化互斥锁,静态初始化方式:普通锁(快速锁) pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; 嵌套锁(递归锁,可以多次上锁): pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP(需要在编译选项中指明-D_GNU_SOURCE); 检错锁: pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁锁
int pthread_mutex_lock(pthread_mutex_t *mutex); 上锁 上锁失败会阻塞,一直等待获取到锁再去上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); 尝试上锁,上锁失败返回错误值然后继续往下执行
int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
//int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); 初始化锁,参数2为状态,填NULL默认快速锁
//使用初始化函数为动态初始化互斥锁,静态初始化方式:快速静态锁 pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
//int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁锁
//int pthread_mutex_lock(pthread_mutex_t *mutex); 上锁 上锁失败会阻塞,一直等待获取到锁再去上锁
//int pthread_mutex_trylock(pthread_mutex_t *mutex); 尝试上锁,上锁失败返回错误值然后继续往下执行
//int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁

pthread_mutex_t mutex; //定义锁变量
pthread_barrier_t barrier;//屏障机制变量,等待所有抢票线程创建完毕在进行统一抢票
pthread_t thread_id[100];//储存线程ID
int p_num=20;//火车票数量
void *create_thread(void *arg)
{
int i = *(int *)arg;
printf(“线程%d–%lu准备就绪\n”,i,pthread_self());
pthread_barrier_wait(&barrier);
while(p_num>0)
{
pthread_mutex_lock(&mutex);
printf(“线程%d-%lu 抢到火车票!”,i,pthread_self());
p_num–;
printf(" 剩余票数:%d\n",p_num);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}

int main(int argc,char **argv)
{
if(argc!=2)
{
printf(“格式:./a.out <线程个数> \n”);
exit(0);
}
pthread_mutex_init(&mutex,NULL);
int i=0;
int data[100]={0};
int cnt=atoi(argv[1]);
pthread_barrier_init(&barrier,NULL,cnt);
for(i=0;i<cnt;i++)
{
data[i]=i;
pthread_create(&thread_id[i],NULL,create_thread,&data[i]);
//sleep(1);
}
for(i=0;i<cnt;i++)
pthread_join(thread_id[i],NULL);
pthread_mutex_destroy(&mutex);
pthread_barrier_destroy(&barrier);
return 0;
}

13.2.2:读写锁
读写锁:适合在一个生产者和多个消费者关系的线程之间,如一个去写,多个线程需要去同时读数据.
读写锁有三个特性:当写上锁(获取锁—也就是请求运行线程)时,不解锁(释放锁)去读上锁会失败;当读上锁或者写上锁时,不解锁,去写上锁会失败;当读上锁时,不解锁再去读上锁不会失败;也就是读上锁可以同时进行,读、写上锁,写、写上锁不可以同时进行

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //读写锁初始化----读写锁变量、读写锁状态

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 读模式加锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //不阻塞

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //写模式加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); //解锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); //销毁读写锁

13.2.3:自旋锁
自旋锁:单纯的阻塞,不会引起任务调度,适合在小线程等待阻塞时,会大大减小CPU资源浪费。(保护消耗时间很短的代码)

int pthread_spin_destroy(pthread_spinlock_t *); //自旋锁销毁

int pthread_spin_init(pthread_spinlock_t *, int); //自旋锁初始化

int pthread_spin_lock(pthread_spinlock_t *); //上锁

int pthread_spin_trylock(pthread_spinlock_t *); //尝试上锁

int pthread_spin_unlock(pthread_spinlock_t *); //解锁

13.2.4:条件变量
条件变量:适合在控制线程运行时。使用条件变量时需要互斥锁的配合,即互斥锁要锁住条件变量等待函数
//初始化与销毁函数
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
//条件变量唤醒函数
int pthread_cond_broadcast(pthread_cond_t *cond); //给全部线程运行的条件变量,当给出这个信号时,当前所有被wait()阻塞的线程全部开始运行
int pthread_cond_signal (pthread_cond_t *cond); //给一个线程运行的条件变量信号,当给出这个信号时,按线程被阻塞顺序开始运行,调用一次此函数线程运行一个
//条件变量等待函数
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); //等待条件变量信号

示例:
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#define MAX_CNT 10

pthread_cond_t cond;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

void thread_func(void arg)
{
int i=
(int
)arg;
printf(“第%d个线程启动.\n”,i);
pthread_mutex_lock(&mutex);
//等待条件变量
pthread_cond_wait(&cond,&mutex);
//公共资源
printf(“ID=%lu,i=%d\n”,pthread_self(),i);
pthread_mutex_unlock(&mutex);
}

//红绿灯类似
void sighandler(int sig)
{
pthread_cond_broadcast(&cond); //唤醒阻塞队列里的所有线程
//pthread_cond_signal(&cond); //唤醒一个线程
}

int main(int argc,char **argv)
{
int i;
pthread_t id[MAX_CNT];
pthread_cond_init(&cond,NULL);
signal(2,sighandler);
for(i=0;i<MAX_CNT;i++)
{
pthread_create(&id[i],NULL,thread_func,&i);
sleep(1); //先让上一个子线程先运行起来
}
for(i=0;i<MAX_CNT;i++)
{
pthread_join(id[i],NULL);
}
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
13.2.5:屏障机制(围栏机制)
围栏机制:适用在当创建完全部线程,也需要同时启用线程时。和条件变量pthread_cond_broadcast()释放全部线程的效果类似,不过这个是当全部线程到达时,就直接开始运行,并且运行先后无序。条件变量需要手动释放,且运行顺序按照线程阻塞顺序来.

int pthread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restrict attr, unsigned count);

int pthread_barrier_wait(pthread_barrier_t *barrier); //等待函数,一般调用在线程一开始。

示例见互斥锁示例!

13.2.6:信号量
信号量:信号量不同于锁机制,它的获取信号量相当于上锁(实质函数内定义的变量在获取到后进行–1),释放信号量相当于解锁(在释放后变量进行++1)。当初始化定义变量值为1时,实现效果和互斥锁相同。(在不叠加的情况下—因为获取和释放可以叠加)
头文件不同,前五种机制使用<pthread.h>

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value); //如果 pshared 的值为 0,那么信号量在进程的线程之间共享, 并且应位于所有线程可见的某个地址(例如,全局变量) 能够,或在堆上动态分配的变量),如果 pshared 不为零,那么信号量在进程之间共享,信号量的值就位于共享内存区域。Value为信号量的初始值
int sem_destroy(sem_t * sem); //注销信号量

int sem_post(sem_t * sem); //释放信号量,相当于解锁
int sem_wait(sem_t * sem); //获取信号量,相当于加锁
int sem_trywait(sem_t * sem); //不阻塞

int sem_getvalue(sem_t * sem, int * sval); //获取信号量值

示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//int sem_init(sem_t *sem, int pshared, unsigned int value);
//int sem_destroy(sem_t * sem);
//int sem_post(sem_t * sem); //释放信号量,相当于解锁,可以叠加,++1
//int sem_wait(sem_t * sem); //等待信号量,相当于加锁,可以叠加,相当于–1
//int sem_trywait(sem_t * sem); //不阻塞
//int sem_getvalue(sem_t * sem, int * sval);//获取信号量的值
pthread_mutex_t mutex; //定义锁变量
pthread_t thread_id[100];//储存线程ID
int p_num=20;//火车票数量
sem_t sem;//设置信号量变量
void *create_thread(void *arg)
{
int i = *(int *)arg;
printf(“线程%d–%lu准备就绪\n”,i,pthread_self());
while(p_num>0)
{
sem_wait(&sem);//获取信号量,只获取不释放,那么全部阻塞,等待创建完毕后再全部一起释放
printf(“线程%d-%lu 抢到火车票!”,i,pthread_self());
p_num–;
printf(" 剩余票数:%d\n",p_num);
sleep(1);
}
}
int main(int argc,char **argv)
{
if(argc!=2)
{
printf(“格式:./a.out <线程个数> \n”);
exit(0);
}
int i=0;
int data[100]={0};
int cnt=atoi(argv[1]);
sem_init(&sem,0,0);//信号量初始化,0,使得开始第一个线程就阻塞
for(i=0;i<cnt;i++)
{
data[i]=i;
pthread_create(&thread_id[i],NULL,create_thread,&data[i]);
}
for(i=0;i<20;i++)//释放火车票一样多的次数
sem_post(&sem);
for(i=0;i<cnt;i++)
pthread_join(thread_id[i],NULL);
return 0;
}

14.驱动学习

启动程序可以分为6部分

BL1为三星不开源的引导程序(8k),BL2最大为14k,所有该部分为uboot引导程序的一部分,来做emmc的初始化、sd卡的初始化等。Uboot.bin为完整引导程序,BL2将比较大的引导程序拷贝在内存中允许,签名文件为验证信息。内核和根文件系统在引导程序运行时检测是否存在.

14.1 交叉编译器安装

安装交叉编译器
1.解压编译器到工作目录(Ubuntu安装编译器会失败,因为编译器为32位的,Ubuntu为64为,所有要安装 sudo apt-get install lib32ncurses5)。

2.将可执行文件目录添加到环境变量

文件.profile里面添加 (RedHat为~/.bash_profile)
PATH=“/home/yjw/work/arm-linux-gcc/opt/FriendlyARM/toolschain/4.5.1/bin:$PATH”
3.重启虚拟机

14.2 u-boot引导程序烧录

  1. 解压uboot源码到工作目录
  2. 执行4个指令: make distclean 、make clean 、make tiny4412_config 、make
    (boards.cfg文件存放当前Uboot支持的芯片型号)
    3.运行脚本向SD卡写uboot引导程序(一般插入的SD卡为sdb文件,自己查询)

烧录成功后,烧录的内容为前四块内容,运行fast_fuse.sh是在烧录过BL1的情况下运行。

14.3 linux内核烧录

  1. 解压linux系统内核到工作目录

  2. 在内核目录下执行 cp tiny4412_linux_defconfig .config

  3. make menuconfig(执行指令前先调小字体)

(ubuntu下make menuconfig错误解决办法: ddddd.安装缺的库 )
4.选中系统类型

5.选中按空格把*去掉,退出并保存

6.编译

7.启动目录

  1. 创建并编写向SD写内核脚本

  2. 向SD卡写入内核程序

  3. 补充-脚本代码主要是这两句起作用,写这两行就绪

14.4 文件系统烧录

14.5 (uboot启动的情况下)将电脑的文件拷贝开发板DDR内存中的方法

  1. 先在PClinux下将xxx.bin文件通过dd命令烧写到SD卡的指定扇区
    再通过uboot的mmc read命令读取到DDR里。mmc 命令中的参数都是 16 进制表示,不是 10 进制表示…EMMC里存放BL1和BL2、签名、uboot的位置和SD卡里差一个扇区,其余的内核和文件系统还是一样.(SD卡)u-boot–49 ,0x290块;bl1—1 ,8k;bl2—17,14k ;签名文件—705,0x140块; 内核—1057,0x3000块 ,文件系统 环境变量单独放在1025,占32块
    先清除EMMC内的程序。
    emmc open 1 //从SD卡启动
    mmc write 1 40000000 0 A000 //把DDR内存中的垃圾数据写到EMMC,让识别不出来是代码。清除大小A000为20M //可以不写

从DDR内存0x48000000的位置向’1’(EMMC)xxx的位置写xxx个块(512字节)。‘1‘是相对于从SD卡启动来说。从哪个设备启动哪个设备编号(device-num)就是0…在uboot内指令都是以16进制传参。
emmc open 1
mmc read 0 48000000 1 10 //读取10块数据到内存,每块占用地址512个,一个地址16位写入一个字节数据。
mmc write 1 48000000 0 10
mmc read 0 48000000 11 20
mmc write 1 48000000 10 20
mmc read 0 48000000 31 290
mmc write 1 48000000 30 290
mmc read 0 48000000 2c1 140
mmc write 1 48000000 2c0 140
mmc read 0 48000000 421 3000
mmc write 1 48000000 421 3000

  1. 通过FAT文件系统直接拷贝。Uboot可以识别FAT文件系统。
    通过fatload命令加载。
    fatload <dev[:part]> [bytes] 尖括号必填项,方括号选填
    如:fatload mmc 0 40000000 zImage不指定大小就是整个文件传入。0就是0:1(暂时不可用)

  2. 先执行loady 再通过串口软件(SecureCRT)的’传输’功能直接传输到DDR指定位置。
    Loady 40000000

  3. 通过ftp服务器网络下载。----没有网卡驱动—无法实现。

  4. 通过uboot内部指令movi进行拷贝
    对设备读写操作,需要先打开,读写完毕,再关闭。
    如:emmc close 1 打开设备 1
    emmc open 1 关闭设备 1
    movi read fwbl1 0 40000000; //从 SD(设备编号为)拷贝 bl1 到 DDR 内存地址
    emmc open 1; //打开 EMMC 设备
    movi write zero fwbl1 1 40000000; //将 DDR 地址处数据写入到 EMMC 对应位置
    emmc close 1; //关闭 EMMC 设备

    movi read bl2 0 40000000; //从 SD(设备编号为 0)拷贝 bl2 到 DDR 内存地址
    emmc open 1; //打开 EMMC 设备
    movi write zero bl2 1 40000000; //将 DDR 地址处数据写入到 EMMC 对应位置
    emmc close 1; //关闭 EMMC 设备
    总共6部分,其余的方式以此类推

    或者精简程序:
    movi r f 0 40008000;emmc open 1;movi w z f 1 40008000;emmc close 1;
    movi r b 0 40008000;emmc open 1;movi w z b 1 40008000;emmc close 1;
    movi r u 0 40008000;emmc open 1;movi w z u 1 40008000;emmc close 1;
    movi r t 0 40008000;emmc open 1;movi w z t 1 40008000;emmc close 1;
    movi r k 0 40008000;movi w k 1 40008000;

14.6 Linux内核及驱动

1.寻找某个命令的实现方法
1—先打开source insight工程,搜索有无和命令相同的文件.比如cmd_xxxxx.c
2—找不到就换个思路根据函数的提示语句,来进行搜索整个项目里面的内容。
最好将提示语句分开搜索,以免有的语句是用%S打印出来.
3—根据找到的地方,再看相关调用的函数,再进行跳转,一步一步深入。
4—根据找到的最底层函数来分析

  1. /dev下设备介绍
    在Linux下3种设备框架:
    1.字符设备 : 键盘、鼠标、触摸屏、…….
    2.块设备: U盘、SD卡、…
    3.网络设备: 有线网卡、无线网卡…
    USB驱动框架-----通用框架

内核管理驱动: 设备号。
设备号: 主设备号+次设备号组成。共32位 宏:MKDEV(ma,mi)实现设备号的合成
主设备号: 表示设备类型。(鼠标、LCD、),高12位,MAJOR(dev) 得到主设备号
次设备号: 表示同类设备的具体设备编号。低20位, MINOR(dev) 得到次设备号
一个创建设备节点的函数:
mknod console c 5 1
mknod <设备节点名称> <设备类型> <主设备号> <次设备号>
设备类型: c 表示字符设备 b表示块设备
3.字符设备
有3种字符设备注册方式:
1.杂项设备注册方式
杂项设备注册的特点:

  1. 注册函数和注销函数就2个
  2. 主设备号固定为10
  3. 次设备号范围: 0~255 255时表示自动分配次设备号
  4. 自动在/dev下创建设备节点

2.早期设备注册方式—linux2.6内核之前使用的注册方式,现在使用的是linux3.5内核。
早期设备注册特点:

  1. 注册注销函数2个
  2. 主设备号范围: 0~255
  3. 当注册一个主设备号之后,就占用了所有的次设备号
  4. 不会自动创建设备节点

3.标准设备注册方式

杂项设备是使用早期设备注册
早期设备是使用标准设备注册
(杂项设备就是一个早期设备注册方式使用的一个典范)

杂项设备调用实现原理:
注册过程:
misc_register杂项设备注册函数(如果选择次设备255,那么注册前内核先自动分配一个没有被占用的次设备号)根据次设备号调用device_create设备创建函数在/dev目录下创建一个设备节点,再调用list_add向杂项设备链表添加该设备信息。
注销过程:
删除/dev下的设备节点,再删除杂项设备链表中的设备信息。(这些注册、销毁函数,创建、删除节点函数,添加、删除链表信息等等的函数都往往成对出现)
调用过程:
应用层函数(如open打开设备文件)----》调用早期设备文件方式的注册函数中创建杂项设备的操作函数集合(中的open函数)-----》改变杂项设备的文件操作集合指向,指向新添加的次设备号的文件操作集合再调用对应设备文件的操作函数集合(的open函数)。因为改变了文件集合指向,所有再调用open、write等就是调用次设备的文件集合
(所以说杂项设备类是在早期设备注册方式上注册,而杂项设备某个设备又是基于杂项设备注册方式)
编写的驱动如果要随时在内核中运行如红外解码,那么就不用写设备注册函数,直接设置IO口,安装驱动即可。如果需要在代码中打开并调用驱动节点,那么只需加上注册和注销函数和文件操作集合即可,但要保证两个驱动都安装时,IO口不会冲突

14.7 开发板挂载nfs文件系统服务器

如果开发板有完整的环境,包括内核和文件系统,那么可以之间在命令行设置ip
Ifconfig eth0 192.168.1.201
再进行挂载.要进行解锁,否则会卡死,还要使处于同一网段下
Mount -t nfs -o nolock <ip>:<共享目录> <挂载目录>

如mount -t nfs -o nolock 192.168.1.200:/home/yjw/rootfs /mnt/
1.搭建NFS文件共享服务器步骤
编辑/etc/exports 文件,示例: /work/ *(rw,no_root_squash,sync)
/home/yjw/rootfs *(rw,no_root_squash,sync)
路径格式:
实际路径 *(rw,no_root_squash,sync)
实际路径:要共享的目录

  • :允许所有的网段访问
    rw :读写权限
    sync:资料同步写入硬盘
    no_root_squash: nfs 客户端共享目录使用者权限
    2.NFS服务器相关命令
    [wbyq@wbyq project] s u d o e x p o r t f s − a / / 目 录 修 改 立 即 生 效 [ w b y q @ w b y q p r o j e c t ] sudo exportfs -a //目录修改立即生效 [wbyq@wbyq project] sudoexportfsa//[wbyq@wbyqproject]sudo exportfs -v //输出当前系统共享的目录
    [wbyq@wbyq project]$ sudo service nfs start //开启NFS服务器
    [wbyq@wbyq project]$ sudo service nfs stop //停止NFS服务器 [确定]
    [wbyq@wbyq project]$ sudo service nfs restart //重启NFS服务器
    3.关闭防火墙(sudo setup),并手动去左上角设置添加一个ip
    可以编写一个脚本启动

启动后可自测试是否设置成功,执行:sodu mount -t nfs 192.168.1.200:/home/yjw/rootfs ./mnt
成功后,取消挂载sudo umount ./mnt
4.网线连接开发板和电脑,挂载文件夹.要先设置虚拟机桥接到电脑以太网卡.

4.1 编译制作根文件系统
工具箱下载地址: https://busybox.net/
下载BusyBox 1.23.2 稳定版本.
然后解压到工作目录,再执行 make menuconfig 配置编译器和安装位置
[wbyq@wbyq busybox-1.23.2]$ make menuconfig
Busybox Settings —>
Build Options —>
(arm-linux-) Cross Compiler prefix // 该条信息下回车,填arm-linux-

Busybox Settings —>
Installation Options (“make install” behavior) —>
(/home/yjw/rootfs) BusyBox installation prefix //该条信息下回车,退格删除默认路径,填自定义路径

设置完成保存退出再进行编译安装

4.2完善根目录系统

  1. 创建文件夹:mkdir lib dev etc/init.d home proc sys root opt tmp var -p
  2. 拷贝动态库
    [wbyq@wbyq lib]$ pwd
    /home/wbyq/rootfs/lib
    [wbyq@wbyq lib]$
    cp /home/wbyq/work/arm-linux-gcc/opt/FriendlyARM/toolschain/4.5.1/arm-none-linux-gnueabi/lib/.so ./

[wbyq@wbyq lib]$
cp /home/wbyq/work/arm-linux-gcc/opt/FriendlyARM/toolschain/4.5.1/arm-none-linux-gnueabi/sys-root/usr/lib/.so ./
拷贝完成用tree命令看,总共146个文件
3. 创建设备文件
[wbyq@wbyq rootfs]$ cd dev/
[wbyq@wbyq dev]$ sudo mknod console c 5 1
[wbyq@wbyq dev]$ sudo mknod null c 1 3
[wbyq@wbyq dev]$ pwd
/home/wbyq/rootfs/dev
4. 拷贝fstab文件,完善ETC
sudo cp /etc/fstab etc/
5. 拷贝inittab文件
cp busybox解压目录/examples/inittab rootfs/etc/
[wbyq@wbyq etc]$ pwd
/home/wbyq/rootfs/etc
[wbyq@wbyq etc]$ cp /home/wbyq/work/busybox/busybox-1.23.2/examples/inittab ./
[wbyq@wbyq etc]$ ls
fstab init.d inittab
6. 修改inittab文件。删除原有全部内容,粘贴下面的内容
::sysinit:/etc/init.d/rcS //系统初始化阶段执行的文件: /etc/init.d/rcS (必须要可执行权限)
console::askfirst:-/bin/sh //指定控制台的脚本解释器
#askfirst表示需要按下回车才可以进入系统 respawn表示开机直接进入系统
::ctrlaltdel:/sbin/reboot //系统重启命令
::shutdown:/bin/umount -a -r //关机时执行的操作: 取消挂载的操作
7. 创建etc/init.d/rcS文件,写入配置代码
[wbyq@wbyq rootfs]$ touch etc/init.d/rcS
[wbyq@wbyq rootfs]$ cat etc/init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s #根据已经安装的驱动自动生成设备节点文件命令。
/bin/hostname wbyq #终端的名称–可以不加
8.修改etc/init.d/rcS权限为可执行。
[wbyq@wbyq rootfs]$ chmod 777 etc/init.d/rcS
9. 创建 etc/profile 文件
[wbyq@wbyq rootfs]$ touch etc/profile
[wbyq@wbyq rootfs]$ cat etc/profile
USER=“id-un”
LOGNAME=KaTeX parse error: Undefined control sequence: \u@ at position 12: USER PS1='[\̲u̲@̲\h \W]\#' PATH=PATH
HOSTNAME=‘/bin/hostname’
export USER LOGNAME PS1 PATH
10. 拷贝分组信息和密码文件到etc目录下
[wbyq@wbyq rootfs]$ sudo cp /etc/passwd etc/
[wbyq@wbyq rootfs]$ sudo cp /etc/group etc/
11. 设置开发板环境变量
开发板选择从EMMC启动,重启开发板,3-2-1之前按下回车。
(1). 设置环境变量 如:
setenv bootargs root=/dev/nfs nfsroot=192.168.1.200:/home/yjw/rootfs ip=192.168.1.201:192.168.1.200:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 lcd=S702
(2). 保存环境变量
(3). 重启开发板
setenv bootargs
root=/dev/nfs nfsroot=<NFS服务器IP地址>
:<NFS共享的目录>
ip=<开发板的IP地址>:<网关>:<子网掩码>::eth0:off init=/linuxrc console=ttySAC0 lcd=S702
(4) 等待读秒完成,引导内核后进入linux系统内

14.8 挂载UbuntuNFS文件服务器

14.9 开发板挂载本地文件系统

本地挂载方法一:
如果开发板有完整的环境,包括内核和文件系统,那么可以之间在命令行设置ip
Ifconfig eth0 192.168.1.201
再进行挂载.要进行解锁,否则会卡死,还要使处于同一网段下
Mount -t nfs -o nolock :<共享目录> <挂载目录>
如mount -t nfs -o nolock 192.168.1.200:/home/yjw/rootfs /mnt/
将文件系统放入SD/EMMC某个分盘,然后设置bootargs环境变量来启动文件系统

1.使用busybox生成文件系统然后完善操作方法和14.7前面一致.
2. 将文件系统打包并放入文件系统下(rootfs)
3. (如果EMMC有完整的环境除了文件系统,请跳至第5步.) 将BL1、BL2、Uboot、内核烧至SD卡,然后设置环境变量:
setenv bootargs root=/dev/nfs nfsroot=192.168.1.200:/home/yjw/rootfs ip=192.168.1.201:192.168.1.200:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 lcd=S702
4. 保存再使用命令将所有包括环境变量复制到EMMC
5.从EMMC启动开发板。进入Uboot命令行,对EMMC用fdisk命令进行分区。分区后对所有盘初始化为ext3 或者ext2 格式,
6.现在bootargs环境变量的值应该和上面的一样,那么就重启,进入Linux命令行,此时挂载在NFS服务器。然后使用mount命令将EMMC任意分盘挂载在 mnt/目录下。
7.解压rootfs.tar解压到挂载目录,并将解压后的mnt/下的rootfs/下所有内容搬移到mnt/下。然后取消挂载重启进入uboot命令行.
8.设置环境变量bootargs改变启动方式,如: set bootargs root=/dev/mmcblk0p2 rootfstype=ext3 init=/linuxrc console=ttySAC0,115200 mmcblk0p2为刚才挂载的那个盘,ext3为该盘的文件格式
本地挂载方法二:
使用厂家工具进行把所有信息从SD烧写到EMMC
1.将友善之臂光盘资料B盘中images的FriendlyARM.ini文件和Superboot4412.bin和linux目录下的ramdisk-u.img文件复制到SD卡下创建的images下。
2.解压F:\系统编程\光驱资料\A\tools\Linux_tools.tgz到linux的工作目录下.
3.删除要打包镜像的文件系统中dev/下的所有文件 :sudo rm /home/yjw/rootfs/dev* -rf
4.使用解压出来的bin目录下的make_ext4fs创建文件系统镜像.使用方法如 :
sudo ./work/linux-tools/usr/local/bin/make_ext4fs -s -l 1024M rootfs.img rootfs 倒数第三项为指定生成的镜像空间,rootfs.img为生成的镜像名,rootfs为使用的文件系统
5.将生成的文件移动到SD卡的images下.
6.重新编译内核,将14.3节内核烧写中步骤重复执行,不执行去掉的星号一步,为了将签名文件也生成在内核中。然后将重新生成的zImage移动到SD卡的images下.
7.修改FriendlyARM.ini文件。将启动安卓内核改为启动Linux内核。并将Linux栏下的内容改为如下格式:
Linux-BootLoader = Superboot4412.bin #引导程序
Linux-Kernel = zImage #修改路径
Linux-CommandLine = root=/dev/mmcblk0p1 rootfstype=ext4 console=ttySAC0,115200 init=/linuxrc ctp=2 skipcali=y ethmac=1C:6F:65:34:51:7E coherent_pool=2M lcd=s702 #不修改
Linux-RamDisk = ramdisk-u.img #修改路径
Linux-RootFs-InstallImage = rootfs.img #修改路径及文件系统镜像名
8.将SD卡插入开发板,设置从SD卡启动,等待出现OS “Linux” XXXX一行后,改为从EMMC启动,然后重启。完成。

14.10 外部中断、定时器、工作队列

1.外部中断:
请求中断号函数:Irq = gpio_to_irq(gpio); //请求中断号,填入gpio返回分配的irq外部中断号
请求中断函数:int request_irq(unsigned int irq,irq_handler_t handler, unsigned long flags,const char *name,void *dev_id); //中断号,中断服务函数,中断触发方式、中断名、往中断服务传入数据的参数
关闭中断函数:disable_irqirq);
释放中断函数:free_irq(key[i].irq,&key[i]);

2.定时器:
struct timer_list timer; //定时器结构体需要关心的几个参数
定时器初始化:
timer.expires=jiffies+msecs_to_jiffies(3000); //装载值
timer.function = timer_function; //定时器中断服务函数
timer.data = 200; //向中断服务函数传参
init_timer(&timer); //初始化
装载值,表示定时多久,赋值格式 jiffies+HZ*5 (HZ当前内核默认为200,而HZ可以表示为1秒,*5意思是定时5s)或者: jiffies+msecs_to_jiffies(5000) :毫秒转换为节拍数函数 jiffies为时间基准
void (function)(unsigned long); //定时器中断服务函数
unsigned long data; //给服务函数传参
/
/启动定时器:添加定时器到定时器队列/
add_timer(&timer);
/关闭、注销定时器/
del_timer(&timer);
//重新设置装载值 当达到装载值进入中断后,如果还要继续产生中断,那么用修改函数重新设定定时
mod_timer(&timer,jiffies+msecs_to_jiffies(3000));

3.工作队列
在内核中,中断服务内代码不宜过于复杂,而且完全不能写延时函数,因为中断由内核接管处理,如果使用延时函数那么会导致进程休眠,那么此时处理中断的进程正是内核,就会造成内核崩溃
那么处理中断就延申出了中断顶半部和中断底半部。顶半部处理时间短的事情,底半部处理时间长,还可以加延时函数。而底半部就是由工作队列来实现。
struct work_struct { //工作队列结构体
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作函数指针 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
}
/工作初始化/
INIT_WORK(&work,work_func);
/添加一个工作到工作队列 主要放在服中断务函数内/
schedule_work(&work);
/销毁工作/
cancel_work_sync(&work);

14.11等待队列、poll、select、epoll、异步IO(signal)

1.等待队列
等待队列适合处理在单输入时,(一个文件设备节点的读写操作)
在没有等待队列之前,当应用层在向内核读取数据或者其他操作时,有时不会接收到数据。例如在等待读取按键键值时,
while(1)
{
Ioctl(fd,READ,&data);
Printf(“%d\n”,data);
}
当没有按键按下时,应用层也会向内核发起读取数据的请求,内核也会给应用层发送一些没有用的数据,那么就会一直浪费CPU的资源。所有引用等待队列可以大大节省CPU的资源 在 Linux 中, 一个等待队列由一个"等待队列头"来管理, 一个 wait_queue_head_t 类型的结构, 定义在<linux/wait.h>中. 一个等待队列头可被定义和初始化。
静态定义:
DECLARE_WAIT_QUEUE_HEAD(name); (预编译时宏被替代为:wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name))
动态定义:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

休眠函数:
wait_event(wq, condition) //休眠不可中断,在没唤醒之前,所有信号都会阻塞,等在休眠被唤醒之后信号才会被执行。如在休眠时Ctrl+c不会结束进程,在执行唤醒函数之后,进程会立马结束
wait_event_interruptible(wq, condition) //休眠可中断,
wait_event_timeout(wq, condition, timeout) //(可以指定时间)(不可中断)
wait_event_interruptible_timeout(wq, condition, timeout) //(可以设置超时时间)(可中断)
唤醒函数:
wake_up(x)//中断非中断类型的休眠都可以唤醒,x为要唤醒的队列头指针,&wait
wake_up_interruptible(x) //唤醒可中断类型
wake_up_interruptible_all(x) //唤醒所有可中断类型的休眠
wake_up_all(x) //唤醒所有休眠
wake_up_nr(x, nr) //唤醒指定个数的所有类型休眠
一般休眠函数放在向应用层传数据前,在所有数据获得后的最好一项前唤醒。然后根据

2.poll
当出现多输入情况下等待队列不能够满足要求,当打开了多个设备文件时,每个设备文件都有一套文件操作函数集合,都有中断服务函数和等待队列,而应用层代码在轮询时,是一个一个设备进行轮询,当上一个设备文件在等待唤醒,那么整个轮询都会休眠进行等待上一个设备唤醒完毕,所以引入了poll来解决这个问题
While(1)
{
Ioctl(fd1,READ,&data);//等待唤醒休眠。。。。(卡住)
Ioctl(fd2,READ,&data);//等待上个设备完成操作
Ioctl(fd3,READ,&data);
}
使用:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#define READ_KEY_VALUE 1234
#define KEY_NUM 4
int data;

int fd[KEY_NUM];
char dev_name[KEY_NUM]={“/dev/wait_key_irq_dev1”,“/dev/wait_key_irq_dev2”,“/dev/wait_key_irq_dev3”,“/dev/wait_key_irq_dev4”};
// struct pollfd {
// int fd; /
文件描述符*/
// short events; /* 事件的类型POLLIN:事件或优先级可读 /
// short revents; /
事件的返回值,内核和编写的驱动赋值 */
// };
struct pollfd fds[KEY_NUM];

int main(int argc,char **argv)
{
int i=0;
for(i=0;i<KEY_NUM;i++)
{
fd[i]=open(dev_name[i],O_RDWR);
if(fd[i]<0)
{
printf(“%s 设备文件打开失败!\n”,dev_name[i]);
return 0;
}
fds[i].fd = fd[i];
fds[i].events = POLLIN;
}
//int poll(struct pollfd *fds, nfds_t nfds, int timeout); //返回成功事件的个数
int cnt = 0;
while(1)
{
cnt = poll(fds,KEY_NUM,0);
if(cnt) //如果有外部中断事件产生
{
for(i=0;i<KEY_NUM;i++)//遍历判断是哪个描述符对应的事件
{
if(fds[i].events & fds[i].revents)
{
ioctl(fd[i],READ_KEY_VALUE,&data);//获取按键值
printf(“0x%x\n”,data);
}
}
}
}

close(fd);
return 0;

}

驱动层接口代码:
static unsigned int key_poll(struct file *file, struct poll_table_struct *poll)
{
unsigned int mask = 0;
/添加进程到等待队列头/
poll_wait(file,&wait,poll);
/判断按键是否按下/
if(condition)
{
condition=0;
mask|=POLLIN;
}
return mask;
}

3.select------poll的升级版
Select最多可以管理1024个文件描述符,在内核驱动中,select和poll的接口完全没有变化,还是poll。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
需要注意的是第一个参数填入的是所有文件描述符中最大的那个并+1.第2、3、4参数分别填入的是读变化类型的事件、写变化类型和其他变化类型。第5是超时时间,填NULL则等待事件集合中有变化才停止阻塞,并返回发生变化的事件个数,填0则立即返回,有变化则返回变化个数,没有返回0,填>0则是设置超时时间,超过设置的时间则返回,返回值和上面相同.
void FD_CLR(int fd, fd_set *set); //将 fd 从 set 结构中清除
int FD_ISSET(int fd, fd_set *set); //检测事件,如果 FD 状态发生变化返回真
void FD_SET(int fd, fd_set *set); //添加文件描述符,可以重复添加多个!
void FD_ZERO(fd_set *set); //每次循环都要清空集合,否则不能检测描述符变化
使用:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define GET_KEY_VALUE 0x12345
char *DEV_NAME[]={“/dev/key_dev1”,“/dev/key_dev2”,“/dev/key_dev3”,“/dev/key_dev4”};
int fd[4];
int data;
fd_set readfds; //定义文件操作集合
int max_fd=0; //保存最大的文件描述符
int event_cnt; //发生事件的数量
int main(int argc,char **argv)
{
int i;
/1. 打开4个设备文件/
for(i=0;i<4;i++)
{
fd[i]=open(DEV_NAME[i],O_RDWR);
if(fd[i]<0)
{
printf(“%s 设备文件打开失败!\n”,DEV_NAME[i]);
return 0;
}
if(fd[i]>max_fd)max_fd=fd[i];//得到最大的文件描述符
}
while(1)
{
/1. 添加文件描述符到操作集合/
for(i=0;i<4;i++)
{
FD_SET(fd[i],&readfds);
}
/2. 进行事件监控/
event_cnt=select(max_fd+1,&readfds,NULL,NULL,NULL);
if(event_cnt)
{
for(i=0;i<4;i++)
{
//判断对应的文件描述符是否产生了读事件
if(FD_ISSET(fd[i],&readfds))
{
ioctl(fd[i],GET_KEY_VALUE,&data);
printf(“select_data=0x%X\n”,data);
}
}
}
/清空文件操作集合中所有文件描述符/
FD_ZERO(&readfds);
}
for(i=0;i<4;i++)
{
close(fd[i]);
}
return 0;
}

4.epoll
同样是poll的升级版,理论可以管理无限个事件,只要内存空间够大,Linux默认每个线程空间10M。接口同样不变.
等待事件:
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epfd :由 epoll_create 生成的 epoll 专用的文件描述符;
epoll_event :用于回传已经检测成功的事件数组;
maxevents :每次监控的最大事件个数;
Timeout (单位是毫秒) :-1 相当于永久阻塞, 0 相当于非阻塞。一般用-1 即可
返回值:事件变化个数
int epoll_create(int size);
该函数生成一个 epoll 专用的文件描述符。它其实是在内核申请一空间,用来存放你监控的描述符 fd 上是否产生了什么事件。 size 就是你在这个 epoll fd 上能存放的最大文件描述符 fd 个数。只要有空间,随便定
注册epoll事件:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能:该函数用于设置某个 epoll 文件描述符上的事件,可以注册事件,批改事件,删除事件。
参数:
epfd:由 epoll_create 生成的 epoll 专用的文件描述符;
op:要进行的事件操作,可能的取值 EPOLL_CTL_ADD 注册、 EPOLL_CTL_MOD 修 改、 EPOLL_CTL_DEL删除。
事件类型定义如下:
#define EPOLL_CTL_ADD 1 //注册
#define EPOLL_CTL_DEL 2 //删除
#define EPOLL_CTL_MOD 3 //修改
fd:将要监控的文件描述符;
event:指向 epoll_event 的指针;
返回值:若是调用成功返回 0,不成功返回-1
使用:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
char *DEV_NAME[]={“/dev/key_dev1”,“/dev/key_dev2”,“/dev/key_dev3”,“/dev/key_dev4”};
int fd[4],data;
int event_cnt; //发生事件的数量
int nfds,epollfd;
#define MAX_EVENTS 4
struct epoll_event ev[MAX_EVENTS],events[MAX_EVENTS];
#define GET_KEY_VALUE 0x12345
int main(int argc,char **argv)
{
int i;
/1. 打开4个设备文件/
for(i=0;i<4;i++)
{
fd[i]=open(DEV_NAME[i],O_RDWR);
if(fd[i]<0)
{
printf(“%s 设备文件打开失败!\n”,DEV_NAME[i]);
return 0;
}
}
/2. 创建存放文件描述符的空间/
epollfd=epoll_create(4);
/3. 初始化事件监听结构体并注册监听的事件/
for(i=0;i<4;i++)
{
ev[i].events = EPOLLIN;
ev[i].data.fd = fd[i];
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd[i],&ev[i]);
}
while(1)
{
/4. 监听事件: 等待事件发生/
nfds=epoll_wait(epollfd,events,MAX_EVENTS,-1);
for(i=0;i<nfds;i++)
{
ioctl(events[i].data.fd,GET_KEY_VALUE,&data);
printf(“epoll_data=0x%X\n”,data);
}
}
for(i=0;i<4;i++)
{
close(fd[i]);
}
return 0;
}

5.异步IO----驱动向应用层发送信号
例如当内核驱动代码产生中断事件时,可以向应用层代码发送一个信号,发送信号函数(kill_fasync(&async,SIGIO,POLL_IN);)只能发送内核中file结构体中struct fown_struct f_owner结构体内的信号类型,参数2、3没有用处,什么时候使用:选择在适当的适合发送信号到应用层。可通过应用层fcntl函数修改文件描述符的信息
异步中断IO的接口是
static struct fasync_struct async;
static int key_fasync(int fd, struct file filp, int on)
{
return fasync_helper(fd, filp, on, &async);
}
应用层使用:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include <signal.h>
#define DEV_NAME “/dev/key_dev”
int fd;
int data;
#define GET_KEY_VALUE 0x12345
//信号处理函数
void sighandler(int sig)
{
printf(“sig=%d\n”,sig);
ioctl(fd,GET_KEY_VALUE,&data);
printf(“signal_data=0x%X\n”,data);
}
int main(int argc,char **argv)
{
int state_flag;
/1. 打开设备文件/
fd=open(DEV_NAME,O_RDWR);
if(fd<0)
{
printf(“%s 设备文件打开失败!\n”,DEV_NAME);
return 0;
}
/2. 绑定要捕获信号/
signal(SIGIO,sighandler);
/3. 给文件描述符设置进程的PID/
fcntl(fd,F_SETOWN,getpid());
/4. 设置当前文件描述符支持异步IO属性/
state_flag=fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,state_flag|O_ASYNC);
while(1)
{
/
/
}
close(fd);
return 0;
}

14.12. 寄存器操作API

物理地址转换虚拟地址: 初始化设备时使用
void __iomem *ioremap (unsigned long phys_addr, unsigned long size)
__iomem *: 返回虚拟地址首地址
unsigned long phys_addr: 物理地址,查询手册得到的地址
unsigned long size:转换长度,一般为一个寄存器的长度,4个字节
取消虚拟地址映射:卸载初始化(退出函数)时使用
Void iounmap (volatile void __iomem *addr);
volatile void __iomem *addr: 填入初始化时生成的虚拟地址首地址

14.13. 内核的同步与互斥

1.原子操作

2.信号量

3.互斥锁

4.自旋锁

5.读写锁

6.顺序锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值