xunwei笔记3---linux系统编程

LINUX系统编程:
===============================
生成LINUX最小系统:
1)使用BUSY BOX-1.21.1软件生成最小系统,需要解压,设置,编译BUSY BOX软件。
(make menuconfig命令,打开设置界面;make命令,编译;make install命令,安装,即生成了最小系统文件)
网址:busybox.net
2)BUSY BOX设置和编译选项如下:
– 进入界面“Busybox Settings”→“Build Optiions”→“Cross Compiler prefix”将其配置为“arm-none-linux-gnueabi-”
– 返回到“Build Optiions”
• 配置二进制文件安装目录
– 进入界面“Installation Options”→“BusyBox installation prefix”将其配置为“../system”(该目录即生成最小文件系统的存储目录)
– 保存退出
3)作者举例的最小系统目录为/home/minilinux/system,添加其它文件到指定目录。(其它文件由迅为提供,该目录中的最小系统此后可以NFS网络文件方式加载)
4)进入/home/minilinux目录,使用make_ext4fs软件打包最小系统文件,命令:make_ext4fs -s -l 314572800 -a root -L linux system.img system(make_ext4fs软件压缩包需要放到/目录下(即根目录),并解压)
5)使用usb-OTG连接开发板,使用fastboot工具在cmd窗口烧写到开发板。(要先在串口界面格式化)
6)只需烧写三个文件:zImage,ramdisk-uboot.img,system.img(对此开发板,uboot文件可以不烧写)
==================================
运行HELLO WORLD程序:
最小LINUX系统,没有图形界面;
在ubuntu下编译.c文件,变为linux可执行文件。根目录下编译环境变量文件:vim -bashrc
编译命令:arm -none-linux-gnueabi -gcc -o 目标文件名 helloworld.c -static
LINUX系统识别优盘,需要挂载,使用挂载命令。
挂载命令:mount /dev/sda1 /mnt/udisk(此后该目录与U盘目录等价操作)
在该目录下输入程序文件名,即执行应用程序。
===================================
挂载TF卡:
与U盘类似,命令:mount /dev/mmcblk1p1 /mnt/udisk1
linux识别TF卡时显示:mmcblk1:p1
须事先创建udisk1目录。
注意:udisk1目录不能已经挂载其它设备,否则要解除挂载,或关机重启。
==================================
如果没有U盘或TF卡:
可以把编译完的hello_world目标程序放到linux最小系统的bin目录下,
然后使用make_ext4fs软件命令打包system.img,然后只烧写该文件到开发板即可。
如果没有权限运行,使用chmod命令修改程序权限即可。
==================================
在ubuntu下创建和删除用户命令:
– 创建用户:useradd xunwei
– 查看用户:cat /etc/passwd
– 删除用户:userdel xunwei
– 提醒:最好不要删除系统自带的用户;最小系统下没有这些命令;

linux用户的组织
– 查看所有帐户信息命令:more /etc/passwd


用户组的操作
– 添加组:groupadd mygroup
– 查看组:cat /etc/group
– 删除组:groupdel mygroup
– 用户组的组织:一个用户可以属于不同的组。
linux中只有唯一的组有权访问系统资源。
=================================
文件权限修改:
权限排列顺序:本用户权限,组权限,其它用户权限
权限数字从左至右:读r,写w,执行x;读,写,执行;读,写,执行;共9位;(采用8进制,即0--7)
权限在二进制下:0无权,1有权;
chmod 777 filename
==========================
cd /(进入根目录)
pwd(显示当前路径)
cd hellowold/(相对路径,前面没有/,最后有/代表目录,进入当前目录的helloworld目录下)
cd ../(进入上级目录)
clear(清除屏幕打印的信息)
==========================
perror()函数与fprintf()函数:由自己调用,协助调试;
==========================
打开文件:
int open(const char *path, int oflags);
• int open(const char *path, int oflags,mode_tmode);
– 参数path表示:路径及文件名。(绝对路径)
– 参数oflags表示:打开文件所采取的方式
O_RDONLY文件只读;O_WRONLY文件只写;O_RDWR文件可读可写;
O_APPEND每次写操作都写入文件的末尾
O_NOCTTY如果路径名指向一个终端设备,不要把这个设备用作控制终端(控制终端应该就是命令输入窗口终端)
O_NDELAY非阻塞(非等待)方式操作文件
O_NONBLOCK如果路径名指向FIFO/块文件/字符文件,则把文件的打开和后继I/O设置为非阻塞模式
O_EXCL如果要创建的文件已存在,则返回-1,并且修改errno的值
O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)
– mode_tmode表示:设置创建文件的权限。可以直接用数字,如:0777(八进制)。
– 返回值:出错返回-1;否则返回文件句柄(文件句柄,就是对应的文件控制块(fcb)结构体中的一个char型描述符,用于区分文件。调用open函数,会自动生成相应的文件控制块,最后将这个char型描述符的值返回成int型,是个正整数)
编程举例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(){
int fd;
char *leds = "/dev/leds";
char *test1 = "/bin/test1";
char *test2 = "/bin/test2";

if((fd = open(leds,O_RDWR|O_NOCTTY|O_NDELAY))<0){
printf("open %s failed!\n",leds);
}
printf("\n%s fd is %d\n",leds,fd);

if((fd = open(test1,O_RDWR,0777))<0){
printf("open %s failed!\n",test1);
}
printf("%s fd is %d\n",test1,fd);

if((fd = open(test2,O_RDWR|O_CREAT,0777))<0){
printf("open %s failed!\n",test2);
}
printf("%s fd is %d\n",test2,fd);
}
=========================================
写文件write函数:
ssize_t write(int fd, const void *buf, count);
- ssize_t是自定义类型,可能是数据长度;
- const void *:指向任意类型的常量对象的指针;
– 参数fd表示:使用open 函数打开文件之后返回的句柄。
– 参数*buf表示:写入的数据
– 参数count表示:最多写入字节数
– 返回值:出错-1,;其它数值表示实际写入的字节数
举例:
//标准输入输出头文件
#include <stdio.h>

//文件操作函数头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

main()
{
int fd;
char *testwrite = "/bin/testwrite";
ssize_t length_w;
char buffer_write[] = "Hello Write Function!";

if((fd = open(testwrite, O_RDWR|O_CREAT,0777))<0){
printf("open %s failed\n",testwrite); //注意:testwrite是个地址,输出字符串%s,与输出字符%c不一样;
}

//将buffer写入fd文件
length_w = write(fd,buffer_write,strlen(buffer_write));
if(length_w == -1)
{
perror("write");
}
else{
printf("Write Function OK!\n");
}
close(fd);
}

===================================
带形参的main函数:
int main(int argc,char **argv)
– 参数argc:argument count,表示命令字符串的个数(除程序名及路径之外,至少输入1个字符串,以空格分开)
– 参数**argv:等价于*argv[],argument vector,存储输入字符串的数组
parameter=形参(formal parameter), argument=实参(actual parameter)。
例如:
#include<stdio.h>
#include<string.h>

int main(int argc,char *argv[])
{
int i,j;
i = atoi(argv[1]);//强制类型转换,字符转为int型
j = atoi(argv[2]);

printf("the Program name is %s\n",argv[0]);//argv[0]存储的是程序名及路径字符串

printf("The command line has %d argument:\n",argc-1);//输出字符串个数,不算程序名字符串

printf("%d,%d\n",i,j);//打印程序名后面2个字符串的Int值,实际argv[3]还有一个null值。

return 0;
}
==================================
ioctl函数:
int main(int argc,char *argv[])
{
int fd;
char *leds = "/dev/leds";//驱动程序路径

//使用ioctl函数将参数传入内核
if((fd = open(leds, O_RDWR|O_NOCTTY|O_NDELAY))<0)
printf("open %s failed\n",leds);
else{
ioctl(fd,atoi(argv[1]),atoi(argv[2]));
printf("ioctl %s success\n",leds);
}
close(fd);

return(1);
}
=====================================
读文件read函数:
ssize_t read(int fd,void *buf,len);
– 参数fd:使用open 函数打开文件之后返回的句柄
– 参数*buf:读出的任意类型数据保存的位置
– 参数len:每次最多读len个字节
– 返回值:错误返回-1,执行成功返回实际读取数据量(ssize_t类型)
举例:
int main(void){
int fd;
char *adc = "/dev/adc";
char buffer[512];//字符类型数组
int len=0, r=0;

memset(buffer,0,sizeof(buffer));//将数组清零

if((fd = open(adc, O_RDWR|O_NOCTTY|O_NDELAY))<0)
printf("open adc err!\n");
else{
printf("open adc success!\n");

len=read(fd,buffer,10); //每次最多读10个字节,放到buffer里,AD的数据为0x000---0xfff

if(len == 0)
printf("return null\n");
else{
r = atoi(buffer);//将buffer中存储的字符类型的AD数据转为整型
r = (int)(r*10000/4095); //Datas transition to Res
printf("res value is %d\n",r);//%d即double方式
}
}
}
=====================================
串口编程:
串口驱动是linux自带的,只需调用即可。
开机启动程序设置:打开etc/init.d/rcs文件,将“路径/程序名”加入即可。
打印串口输出的开机信息:控制---日志设置---standard---保存位置
串口初始化举例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>

int set_opt(int,int,int,char,int);
//"/dev/ttySAC3"是con2,靠近耳机接口的串口
void main()
{
int fd,nByte;
char *uart3 = "/dev/ttySAC3";//驱动路径
char buffer[512];
char *uart_out = "please input\r\n";
memset(buffer, 0, sizeof(buffer));
if((fd = open(uart3, O_RDWR|O_NOCTTY))<0)//打开串口
printf("open %s is failed",uart3);
else{
set_opt(fd, 115200, 8, 'N', 1);//初始化串口,调用迅为打包的函数,内部配置了结构体termios参数。
write(fd,uart_out, strlen(uart_out));//串口发送一次
while(1){
while((nByte = read(fd, buffer, 512))>0){//串口接收一次
buffer[nByte+1] = '\0'; //结尾插入字符串结束符
write(fd,buffer,strlen(buffer));//串口发送一次,将收到的发回去
memset(buffer, 0, strlen(buffer));//发送完毕清空缓存
nByte = 0;
}
}
}
}
=====================================
TFTP服务器搭建:(简单文件传输)
将ubuntu作为服务器,用开发板通过交换机连接到ubuntu,并下载文件,比挂载U盘方便;
无法列出文件名和目录,也不能认证登录,只是直接下载。
1)在ubuntu安装软件xinetd,命令:sudo apt-get install xinetd;(ubuntu应连接互联网:ping baidu.com,否则apt-get命令无法使用。root用户无需输入sudo)
2)在ubuntu安装软件tftp 和tftpd,命令:sudo apt-get install tftp tftpd;
3)在ubuntu编辑和配置/etc/xinetd.d/tftp文件;(内含服务器的文件存储目录,应先创建这个目录,并修改权限为777)
4)在ubuntu重启xinetd服务,命令:sudo /etc/init.d/xinetd restart;
5)在ubuntu中自测试搭建是否成功:新开窗口,输入命令tftp 127.0.0.1,进入服务器,输入get test获取文件(事先在服务器目录准备好test文件),输入q退出。
6)将开发板设置到与ubuntu同一网段;(修改/etc/eth0-setting文件,注意路由器的网址范围限制,查看ubuntu的IP地址命令:ifconfig)
7)下载文件命令:tftp -g -l test -r test +IP地址(ubuntu服务器地址,如:192.168.0.225)(前面test是服务器内文件名,后面test是自定义文件名)
======================================
NFS服务器搭建:(用于启动最小Linux系统,不适合启动安卓,太大太慢)
功能:不同的操作系统彼此共享文件,可以通过NFS 挂载远程主机的目录,访问该目录就像访问本地目录一样;可以实现在线启动文件系统或调试应用程序;
1)在ubuntu系统安装NFS服务软件,命令:apt-get install nfs-kernel-server;
2)修改/etc/exports文件,最后一行添加:/home/minilinux/ *(rw,sync,no_root_squash);
其中,/home/minilinux是共享目录,*代表允许所有的网络段访问,rw是可读写访问权限,sync是资料同步写入内存和硬盘,no_root_squash是NFS客户端使用者的权限,如果客户端使用的是root用户,那么对于该共享目录而言,该客户端就具有root权限;
3)重启portmap服务,命令:/etc/init.d/portmap restart
4)重启nfs服务器,命令:/etc/init.d/nfs-kernel-server restart
在ubuntu窗口下测试搭建是否成功:
1)命令:mount -t nfs localhost:/home/minilinux/system /mnt(最小文件系统在/home/minilinux目录下,根据实际情况设置)
2)命令:df(查看是否挂载成功)
=====================================
内核修改:(以适应NFS服务器访问,开发板上只有一个内核运行,修改后方可从NFS启动最小LINUX系统)
1)进入内核解压目录,输入命令:cp -r config_for_linux_scp_elite .config(复制原文件为QTE的config文件,用于配置,-r可以不加)
2)进入内核设置界面,命令:make menuconfig,设置如下几项:
进入“Networking support”→“Networking options”→选上“IP: kernel level autoconfiguration;
进入“File systems”→“Network File Systems”选中“NFS client support”,“NFS client support for NFS version 3”,
“NFS client support for the NFSv3 ACL protocol extension”,“NFS client support for NFS version4”,
“NFS client support for NFSv4.1”,“Root file system on NFS”
进入“Boot options”→“Default kernel command”修改路径:root=/dev/nfs rw nfsroot=192.168.1.103:/home/minilinux/system
ip=192.168.1.230:192.168.1.103:192.168.1.1:255.255.255.0:iTOP:eth0:off rootfstype=ext4 init=/linuxrc console=ttySAC2,115200
(这个路径和IP等是在一行内,没有回车;192.168.1.103:/home/minilinux/system 表示挂载的nfs服务器ip 是192.168.1.103,挂载的目录是/home/minilinux/system;192.168.1.230 是我们开发板的ip地址,第二个192.168.1.103 是nfs服务器的ip,第三个ip192.168.1.1是开发板的路由器网关,255.255.255.0 是子网掩码,开发板与nfs服务器在同一网段!iTOP 是开发主机的名字(一般无关紧要,可以随便填写),eth0是网卡设备的名称)
3)编译内核,下载到开发板;(此后,从服务器启动最小linux文件系统,该系统文件需事先放在/home/minilinux/system目录下,作者没讲这部分内容)
=====================================
使用NFS功能测试程序:
将编译好的程序放到NFS服务器的共享目录后,开发板也会同步拥有该程序。例如:
服务器/home/minilinux/system/bin目录与开发板/bin目录同步。
NFS服务器的/home/minilinux/system目录,对应开发板的根目录。
=====================================
没有交换机或路由器,网络直连情况下的tftp和nfs配置:
1)开发板与电脑网线直连,将开发板、电脑、ubuntu的IP地址设为同一网段,虚拟机软件设为桥接模式;
2)配置ubuntu前,开发板应与电脑事先连接好,否则可能显示无连接。然后使用Ping命令测试是否互通;
3)将前述搭建方式的IP地址做相应修改;
4)对于NFS功能,则需要重新配置内核,将“Boot options”→“Default kernel command”中的ip地址做相应修改,并编译下载内核;
====================================
NFS共享目录:(参考前述服务器搭建,最小LINUX系统已经在开发板上)
1)修改ubuntu配置文件/etc/exports,最后一行添加共享目录路径:/home/topeet/linux/ *(rw,sync,no_root_squash)
2)如果Ubuntu上没有上述共享目录,则需要新建/home/topeet/linux目录;
3)修改内核选项:
进入Networking support
– 选中Networking options然后进入
• 选中IP: kernel level autoconfiguration

进入File systems
– 选中Network File Systems然后选中以下选项
• “NFS client support”,“NFS client support for NFS version 3”,“NFS client support for the NFSv3 ACL protocol
extension”,“NFS client support for NFS version 4”,“NFS client support for NFSv4.1”,“Root file system on
NFS”一共六个选项
4)内核的网卡驱动可能有问题,会打印无用信息。注释掉网卡调试信息drivers/net/usb/dm9620.c,关键词LEN_PLOAD,注释掉三行打印信息。
5)编译内核,下载到开发板。
6)在开发板新建目录/mnt/nfs;
7)挂载NFS目录,命令:mount -t nfs -o nolock 192.168.3.84:/home/topeet/linux /mnt/nfs(IP地址根据实际情况修改)
=======================================
延时函数:(头文件:#include<unistd.h>)
linux系统编程中常用的延时函数:sleep、usleep等
linux内核中的常用的延时函数:ndelay、udelay、mdelay等
unsigned int sleep(unsigned int seconds);
例如:sleep(1),即延时1秒。
返回值:如果延时成功则返回0,如果延时过程中被打断,则返回剩余的秒数。
int usleep(useconds_t usec);
usec需要小于1000000
例如:usleep(10),表示延时10微秒。
延时成功则返回0,失败则返回-1
======================================
GMT时间:格林尼治标准时间,即世界时区基准时间。已经不再被作为标准时间使用,而是使用下面的UTC时间。
UTC时间:世界统一时间,与GMT规则等同,但更精确,采用原子秒长计时。
UNIX纪元时间:从1970年1月1日0时算起,到现在的秒数。
机器日历时间:对Linux来说,与UNIX纪元时间等同。
======================================
时间函数:(头文件:#include<time.h>)
获取机器时间函数
time_t time(time_t *t);
参数*t:以秒为单位的机器时间
返回值:如果参数为NULL,则返回机器时间;错误返回-1;
time_t类型实际是一个long int类型
c语言语法:0x%08x:先显示0x,随后是8位宽的16进制数;
======================================
时间转换函数:(头文件:#include<time.h>)
将时间转化为字符串格式:char *ctime(const time_t *timep);
将时间转化为格林威治时间:struct tm *gmtime(const time_t *timep);
时间转换为字符格式:char *asctime(const struct tm *tm);
时间转化为本地时间:struct tm *localtime(const time_t *clock);
tm结构体:包含tm_sec;tm_min;tm_hour;int tm_mday等成员变量及函数ctime;
举例:
#include <stdio.h>
#include <time.h>
int main(void){
time_t timep;
struct tm *tblock;

time(&timep);
printf("ctime/timep is %s\n",ctime(&timep));
printf("asctime is %s\n",asctime(gmtime(&timep)));

tblock = localtime(&timep);
printf("localtime is :%s\n",asctime(tblock));
printf("localtime is:%s\n",ctime(&timep));
return 0;
}
=========================================
高精度的设置时间函数和读取时间函数:(头文件:#include<sys/time.h>)
int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone
*tz);
– 参数tv:用于保存获取的时间
– 参数tz:可以缺省,传入NULL
– 上面的函数比time要高6个数量级,可以达到微妙,这个精度就可以粗略的计算代码执行时间了;
timeval结构体:包含tv_sec(秒),tv_usec(微秒)成员变量。(tv是timeval缩写)
举例:(测试CPU运行速度)
#include<time.h>
#include<sys/time.h>
#include<stdio.h>

void function() //耗时操作
{
unsigned int i,j;
double y;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++)
y=i/(j+1);
}

main()
{
struct timeval tpstart,tpend;
float timeuse;

gettimeofday(&tpstart,NULL); //记录开始时间
function();
gettimeofday(&tpend,NULL); //记录结束时间

timeuse = 1000000*(tpend.tv_sec-tpstart.tv_sec)+
tpend.tv_usec-tpstart.tv_usec; //计算差值
timeuse /= 1000000;

printf("Used Time:%f\n",timeuse);
}
=========================================
ubuntu终端下man命令的使用:
man 1:一般命令。常见的linux命令,例如ls,cd,cat等等
man 2:用来放内核提供的系统调用或者函数。例如man 2 fork等
man 3:C库函数。
man 4:特殊文件,例如设备和驱动程序
man 5:文件格式。包括完全使用文本配置文件定制系统的操作,大量的配置文件,网络服务列表,可用的shell列表等等
man 6:游戏和屏幕保护程序。
man 7:杂类文件。
man 8:系统管理命令,超级用户可能需要用到它们。
举例:man 3 sleep
================================
查看文件索引号:
命令:ls -i;ls -al;ls -ail;
硬链接:文件名不同,文件属性相同,内容也相同的文件。相当于同步更新的副本。
软链接:文件名不同,文件属性不同,内容相同的文件。相当于快捷方式。
=================================
文件函数:(所需的头文件用man命令查看)
函数int stat(const char *path, struct stat *buf);//直接查看文件信息
– 参数*path:文件路径(含文件名)
– 参数*buf:文件信息
– 返回值:成功为0,否则为-1
函数int fstat(int fd, struct stat *buf);//先打开文件,再查看文件信息
– 参数fd:文件描述符
– 参数*buf:文件信息
– 返回值:成功为0,否则为-1
函数int lstat(const char *path, struct stat *buf);
– 参数*path:文件路径
– 参数*buf:返回文件的信息,类似于stat.但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是
由该符号链接引用文件的信息。(也就是返回快捷方式的信息,而不是源文件信息)
– 返回值:成功为0,否则为-1
语法:%ld:长整型
stat结构体:含有与文件描述相关的成员变量,包括索引号,访问时间,数据量等等。
------------------------------------------
int chmod(const char *path, mode_t mode);//修改文件权限
– 参数*path:文件路径。(含文件名)
– 参数mode:直接使用数字即可。和前面命令中chmod 777 xxx 中的777 这
个参数含义类似,也可以使用文档中的组合值。
– 返回值:成功返回0,错误返回-1。
int fchmod(int fd, mode_t mode);//先打开文件,再修改权限
– 参数fd:文件描述符。
– 参数mode:直接使用数字即可。和前面命令中chmod 777 xxx 中的777 这
个参数含义类似,也可以使用文档中的组合值。
– 返回值:成功返回0,错误返回-1。
-------------------------------------------
char *getcwd(char *buf, size_t size);//获取当前文件的路径
– 参数*buf:保存当前文件路径的缓冲区
– 参数size:在现代linux 中,buf 的长度至少可以为255 字节
– 返回值:成功返回指向当前目录的指针,和buf 的值一样,错误返回NULL
char *getwd(char *buf);//该函数已经过时
char *get_current_dir_name(void);
– 参数:无
– 返回值:成功返回指向当前目录的指针,错误返回NULL
------------------------------------------
int mkdir(const char *pathname,mode_t mode);//创建目录
– 参数*path:文件路径。
– 参数mode:直接使用数字即可。和前面命令中chmod 777 xxx 中的777 这
个参数含义类似,也可以使用文档中的组合值。
– 返回值:成功返回0,错误返回-1。
int rmdir(const char *pathname);//删除目录
– 参数*pathname:文件和目录的路径
– 返回值:成功返回0,错误返回-1
--------------------------------------------
int chdir(const char *path);//跳转到其它目录
– 参数*path:目标文件路径。
– 返回值:成功返回0,错误返回-1。
-------------------------------------------
DIR *opendir(const char *name);//打开目录,相当于ls命令
– 参数:目录的路径。
– 返回值:成功返回指向目录流的指针,错误返回NULL
int closedir(DIR *dirp);//关闭目录
– 参数:opendir 返回的dir 指针
– 返回值:成功返回0, 失败返回-1
struct dirent *readdir(DIR *dirp);//读目录信息函数
– 参数dirp:opendir 函数打开目录后返回的文件指针。
– 返回值:成功返回指向dirp 的指针dirent ,错误返回NULL。
=================================
使用ln命令可以创建文件的硬链接,也有相应函数link。
int link(const char *oldpath, const char *newpath);//硬链接函数
– 参数*oldpath:已有的文件路径。
– 参数*newpath:新建的硬链接文件路径。
– 返回值:成功返回0,错误返回-1。
符号链接也叫软链接,symlink函数。
int symlink(const char *oldpath, const char *newpath);//软链接函数
– 参数*oldpath:已有的文件路径
– 参数*newpath:新建的符号链接文件路径
– 返回值:成功返回0,错误返回-1
int unlink(const char *pathname);//解除链接函数
– 参数*pathname:链接文件的路径
– 返回值:成功返回0,错误返回-1
– 若unlink指向软链接,则删除软链接;若指向最后一个硬链接,则相当于删除文件
=================================
文件复制:通过打开、读取、写入函数实现,没有复制函数。相当于cp命令。
文件移动:命令为mv,函数为rename
int rename(const char *oldpath, const char *newpath);//文件移动
– 参数*oldpath:旧的文件路径(含文件名)
– 参数*newpath:新的文件路径
– 返回值:成功返回0,错误返回-1
===============================
进程查看命令:top,ps
进程终止命令:kill 进程号
获取子进程ID的函数:pid_t getpid(void)
– 参数:无
– 返回值:成功返回进程号
获取父进程ID的函数:pid_t getppid(void);
– 参数:无
– 返回值:成功返回父进程号
在当前程序中,可以使用exec函数族运行其它程序:
exec函数族参数:
– “l”和“v”表示参数是以列表还是以数组的方式提供的
– “p”表示这个函数的第一个参数是*path,就是以绝对路径来提供程序的路径,也可以以当前目录作为目标
– “e”表示为程序提供新的环境变量
举例:
int main(void)
{
if(execl("/mnt/udisk/helloexec","helloexec","execl",NULL) == -1){//运行的helloexec程序,需事先准备好
perror("execl error");//程序已经跳转走,如果正常execl不返回错误,这条代码不会执行!
exit(1);
}
//程序已经跳转走,如果正常execl不返回错误,下面的代码不会执行!
printf("execl error!\n");
return 0;
}
在当前程序中,可以使用fork函数创建和当前程序一模一样的进程,叫子进程,当前的程序叫父进程。
创建进程函数:pid_t fork(void);
– 参数:无
– 返回值:执行成功,返回子进程pid给父进程,返回0给子进程;出现错误,返回-1给父进程。执行失败的唯一情况是内存不够或者id号用尽,(超过32768)
不过这种情况几乎很少发生。
fork函数会创建一个新程序,与当前程序一模一样,只是新程序的fork返回值为0,当前程序的fork返回值是新程序的pid。除非创建失败。
if(fork() == 0):只会在子进程中被执行,因为子进程的fork返回值是0.
exit函数会导致进程执行后退出,即pid消失。
其它exec函数:execv,execlp,execvp,execle,execve;
=====================================
无名管道:
int pipe(int pipefd[2])
– 参数pipefd[0]:用于读管道
– 参数pipefd[1]:用于写管道
– 返回值:执行成功返回0,失败返回-1
注意:只能是具有亲缘关系的进程间可以使用无名管道。
实际是创建了两个文件,一个只写,另一个只读。函数形参就是文件指针。
=====================================
有名管道:
int mkfifo(const char *pathname, mode_t mode)
– 参数*pathname:路径及管道名称,若在当前目录只写名称即可
– 参数mode:管道的权限,如:0777
– 返回值:成功返回0,错误返回-1
能实现没有亲缘关系的进程间通信,数据是先进先出,一个进程写,另一个进程读。
getchar();//扫描输入的字符,其返回值是ASCII码,可存于int变量中,再用putchar()将int变量输出,即将ASCII码变回字符。
putchar();//将字符输出或打印到标准输出设备上。
putc();//将ASCII码所代表的字符写到文件中。
======================================
消息队列:
消息队列就是具有特定的格式以及特定优先级的记录的链表。
消息接收:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)
– 参数msqid:消息队列的标识码
– 参数*msgp:指向消息缓冲区的指针,此位置用来暂时存储接收的消息,是一个用户可定义的通用结构(见下面消息结构体)
– 参数msgsz:消息的长短
– 参数msgtyp: msgtyp等于0,则返回队列的最早的一个消息
msgtyp大于0,则返回第一个类型为msgtyp的消息
msgtyp小于0,则返回第一个类型小于或等于msgtyp的绝对值的消息
– 参数msgflg:在队列没有数据的情况下所应采取的行动,为0表示忽略。
– 返回值:成功返回数据长度,错误返回-1
消息发送:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
– 参数msqid:消息队列的标识码
– 参数*msgp:指向消息缓冲区的指针,此位置用来暂时存储发送的消息,是一个用户可定义的通用结构
– 参数msgsz:消息的长短
– 参数msgflg:标志位
– 返回值:成功返回0,错误返回-1
消息结构体:
结构体msgp,是一个标准的通用结构
– struct msgstru{
long mtype; //必须大于0,消息类型
char mtext[nbyte];}
消息创建,获取消息队列标识码:(接收端和发送端都要调用该函数,通过key联系在一起)
int msgget(key_t key, int msgflg)
– 参数“key”:消息队列关联的标识符,自定义数字
– 参数“msgflg”:消息队列的建立标志和存取权限。
IPC_CREAT:如果内核中没有此队列则创建它;
IPC_EXCL:当和IPC_CREAT一起使用时,如果队列已经存在,则失败
– 返回值:执行成功则返回接收和发送的消息队列的标识码,否则返回-1

自定义的消息缓冲区结构体:(用于存储待发数据和待收数据,发送程序和接收程序都要有自己的缓冲区例化体)
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};

删除消息队列:(接收完毕后,由接收端删除)
msgctl(msgid, IPC_RMID, 0)
-参数1:消息队列的标识码;
-成功返回0,失败返回-1;
==================================
信号:(类似于软件中断)
unsigned int alarm(unsigned int seconds)
– 参数seconds:闹钟的时间,单位为秒
– 返回值:成功返回0 或者返回剩余时间;错误返回-1
sighandler_t signal(int signum, sighandler_t handler);
– 参数signum:等待的信号,例如:SIGALRM(闹钟)
– 参数handler:信号到来之后,触发的处理方式,可以是个函数
– 返回值:成功返回0,错误返回-1
常见信号:
– SIGALRM:闹钟
– SIGHUP:终端发出的结束信号
– SIGINT:键盘的ctrl+c
– SIGKILL:kill命令产生的信号
– SIGSTOP:键盘的ctrl+z
函数pause():–用于进程挂起直到捕捉到信号
举例:
signal(SIGALRM, handler);//handler是中断函数名
alarm(5);//闹钟5秒
信号集:
信号集变量类型:sigset_t sigset;
结构体类型:struct sigaction act;
函数:
sigemptyset(&sigset); //清空信号集
sigaddset(&sigset, SIGINT); //增加信号SIGINT到信号集sigset,SIGINT可能代表中断
act.sa_handler = handler; //指定中断函数
act.sa_flags = 0; //指定标志位
sigaction(SIGINT, &act, 0); //在SIGINT信号下,设置act的信号处理方式
sigprocmask(SIG_SETMASK, &sigset, 0);//信号屏蔽字设置为sigset
sigpending(&ign);//将被阻塞待处理的信号写入ign
sigismember(&ign, SIGINT);//测试SIGINT信号是否已入信号集
sigdelset(&sigset, SIGINT); //删除信号集
sigsuspend(&sigset); //将屏蔽字替换为sigset信号集,然后挂起进程
参考资料:https://www.cnblogs.com/52php/p/5815125.html
==================================
信号量:(协调进程之间对公共资源的访问,例如对stdout输出缓冲区资源)
创建信号量:int semget(key_t key,int nsems,int semflg)
- 参数key:不相关的进程可以通过它访问一个信号量,是自定义的一个值,为0时是该进程私有信号量。
- 参数nsems:指定需要的信号量数目,实际使用通常是1
- 参数semflg:是一组标志,权限+创建(如果不存在则创建,存在则打开)
- 返回值:信号量标识符,其他的信号量函数使用该返回值进行操作
例如:sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
信号量创建后,会产生一个结构体:struct sembuf,其它函数通过标识符操作这个结构体。
结构体如下:
struct sembuf{short sem_num;short sem_op;short sem_flg;}
- sem_num:信号量在信号量集中的序号,从0开始
- sem_op:在信号量初始值基础上相加的数据,-1即P等待,+1即V发送信号
- sem_flg:信号量标志,通常为SEM_UNDO,这样,进程终止时,操作系统会自动撤消信号量。
操作信号量:int semop(int semid,struct sembuf *sops,size_t nsops)
- semid:信号量集的识别码
- sops:指向信号量操作结构体的数组首地址的指针
- nsops:信号量操作结构体的数量,恒大于或等于1
- 返回值:调用成功返回0,失败返回-1。
例如:semop(sem_id, &sem_b, 1);//其中的sem_b是自己事先声明的结构体,内部变量要先赋值,然后调用semop对创建的信号量操作,也就是对那个创建的结构体操作。
控制信号量:int semctl(int semid,int semnum,int cmd, union semun arg)
- semid:信号量集标识符
- semnum:操作信号在信号集中的编号,从0开始
- cmd:指出要操作的具体命令,SETVAL设置信号量集中的一个信号量的值,IPC_RMID将信号量集从内存中删除
- semun arg:内核中使用的联合体变量,只有当cmd取某些特定的值的时候,才会用到,例如,初始化信号量。(另外,在cmd命令IPC_STAT/IPC_SET中使用该联合体,它代表内核中所使用的信号量数据结构的一个复制 ,涉及成员:buf;在cmd命令GETALL/SETALL中使用该联合体,代表指向一个整数值数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array)
使用semctl函数可以初始化信号量,或删除信号量。其cmd命令有多种。事先声明一个semun类型联合体,并赋值,然后用semctl函数调用这个联合体,最终控制信号量的联合体。
信号量联合体需要在main前声明,如下:
union semun
{
int val; //信号量初始值
struct semid_ds *buf;
unsigned short *arry;
};
ubuntu命令:./seml a&./seml(执行两次seml程序,第一次是seml a,第二次是seml。在&之后可以换行输入)
======================================
共享内存:(在各种进程间通信方式中具有最高的效率,通过信号量进行协调)
共享内存的结构体:
struct shared_use_st { int written; char text[TEXT_SZ];};
- written作为一个标志,非0:表示可读,0表示可写
- text记录写入和读取的文本
共享内存创建:
int shmget(key_t key, size_t size, int shmflg)
- key:键值,指向该新建的共享内存对象,被其他函数和进程引用
- size:新建的内存大小
- shmflg:标识符,权限+创建
- 返回值:共享内存的标识符,返回-1代表创建失败
例如:shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT)
共享内存地址映射:
void *shmat(int shmid, const void *shmaddr, int shmflg)
- shmid:之前创建的共享内存标识符
- shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL(或0值)是让内核自己决定一个合适的地址位置
- 返回值:共享的内存地址,失败返回-1
例如:shmat(shmid, (void*)0, 0);
- shmflg:SHM_RDONLY:为只读模式,其他为读写模式(根据示例推断:为0代表读写模式)
语法:(void*)-1;//将-1转化为指针地址,这样书写便于移植
共享内存控制:
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
- shmid:共享内存标识符
- cmd:如果是IPC_RMID,则删除这片共享内存
- buf:共享内存的管理结构体指针,该结构体struct shmid_ds定义如下。
- 返回值:失败返回-1
例如:shmctl(shmid, IPC_RMID, 0);//删除共享内存
struct shmid_ds {
struct ipc_perm shm_perm;
int shm_segsz;
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
unsigned short shm_cpid;
unsigned short shm_lpid;
short shm_nattch;
unsigned short shm_npages;
unsigned long *shm_pages;
struct vm_area_struct *attaches;
};
总体过程:
1)创建共享内存,两个main程序或进程通过shmget函数,并使用相同key键值,来实现创建和共享;
2)调用shmat函数时,应事先声明一个指针shm,并将返回值(内存地址)赋值给shm。(shm声明:void *shm = NULL;)
3)然后将该指针转换为结构体类型指针:shared = (struct shared_use_st*)shm; (shared声明:struct shared_use_st *shared = NULL;)
4)利用结构体指针shared判断共享内存的读写状态,并进行读写。(while(shared->written == 1))
5)操作完毕后,使用shmdt(shm)进行分离,从当前进程的地址映射中分离共享内存。(分离失败,则shmdt返回-1;分离后不可用,不是真正删除)
6)最后,由最后一次访问共享内存的进程执行删除操作,shmctl(shmid, IPC_RMID, 0);在此之前访问的进程只做分离,不做删除。
=======================================
TCP网络通信:面向连接的传输控制协议(头文件:#include <sys/socket.h>,在上层应用程序中编程)
结构体定义:(在netinet/in.h中)
struct sockaddr_in {
short sin_family;//Addressfamily,一般来说AF_INET(地址族)PF_INET(协议族)
unsigned short sin_port;//Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字
struct in_addr sin_addr;//Internet address
unsigned char sin_zero[8];//Same size as struct sockaddr,没有实际意义,只是为了跟SOCKADDR结构在内存中对齐
};
socket函数:创建套接口,用于根据指定的地址族、数据类型和协议来分配一个套接口的描述符及其所用的资源。
int socket(int domain, int type, int protocol);
- domain:目前仅支持AF_INET格式,也就是ARPA Internet地址格式;
- type:新套接口的类型描述,SOCK_STREAM提供面向连接的稳定数据传输,即TCP协议;
- protocol:套接口所用的协议。如不想指定,可用0,表示缺省;
- 返回值:创建的套接口描述符,失败返回-1;
例如:socket(AF_INET, SOCK_STREAM, 0);
bzero函数:将某段地址清零。用于给struct sockaddr_in变量清零。
extern void bzero(void *s, int n);
- s:要置零的数据的起始地址;
- n:要置零的数据字节个数。
例如:bzero(&s_add,sizeof(struct sockaddr_in))
inet_addr函数:将小数点分隔的十进制的IP地址转换成一个长整型数(u_long类型)
in_addr_t inet_addr(const char *cp)
例如:inet_addr(argv[1])//将命令行中的第二个参数(IP地址)变成长整型数
htons函数:将一个无符号短整型数(16位)转换成网络字节顺序的无符号短整型数。(我理解为hex to net short)
u_short htons( u_short hostshort)
例如:s_add.sin_port=htons(portnum)
connect函数:建立与一个主机端口的连接
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen)
- sockfd:本地待连接的套接口的描述符
- serv_addr:想要连接的主机结构体(sockaddr_in)地址指针
- addrlen:缓冲区的长度(struct sockaddr)
例如:connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr));//其中的s_add结构体应事先声明和赋值
struct sockaddr:尚不清楚其定义
读取数据,直接用read函数,例如:read(cfd, buffer, 1024)
断开连接:close(cfd);
--------------------------
bind()绑定函数:通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen)
返回值:失败返回-1;
例如:bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr));//其中的s_add结构体应事先声明和赋值
监听函数:指定相应的套接字变为被动连接套接口,使得该进程可以接受其它进程的请求,从而成为一个服务器进程。
int listen(SOCKET sockfd, int backlog)
- sockfd:一个已绑定但未被连接的套接字描述符
- backlog:连接请求队列的最大长度(尚不清楚其含义)
返回值:失败返回-1;
例如:listen(sfp,5);
接受函数:默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字(用新套接字来发送数据)
int accept(int sockfd, struct sockaddr * addr, socklen_t* len)
返回值:失败返回-1;
例如:accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
ntohl函数:将一个无符号长整形数从网络字节顺序转换为主机字节顺序(我理解为net to hex long)
uint32_t ntohl(uint32_t netlong);
- netlong:一个以网络字节顺序表达的32位数
ntohs()函数:与前述htons函数相反。
uint16_t ntohs(uint16_t netshort)
发送函数:(使用accept函数返回的新套接字)
ssize_t send (int s,const void *msg,size_t len,int flags)
- s:指定发送端套接字描述符
- msg:一个存放应用程式要发送数据的缓冲区
- len:指明实际要发送的数据的字节数
- flags:一般置0
例如:send(nfp, buffer, strlen(buffer), 0);
------------------------------
通信流程:
1)服务器端:创建socket,清零结构体,绑定,监听,接受;(socket,bzero,bind,listen,accept函数)
2)客户端:创建socket,清零结构体,连接;(socket,bzero,connect函数)
3)双方交替读写数据;(read,send函数)
4)关闭套接字;(close函数)
=====================================
UDP网络通信:(无连接的传输层协议,提供面向事物的简单不可靠信息传送服务,资源消耗小,处理速度快的优点,适合音频,视频及刷新类的数据)
与TCP的服务器不同,UDP的服务器不需要listen和accept函数,客户端不需要connect函数;同样使用struct sockaddr_in结构体;
通信流程:
1)服务器端:创建创建socket,清零结构体,绑定;(socket,bzero,bind函数)
2)客户端:创建socket,清零结构体;(socket,bzero函数)
3)相互通信;(sendto,recvfrom函数)
4)关闭套接字;(close函数)
整个过程使用同一个套接口标识符。
发送函数:
ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len)
- socket:套接口的描述符
- message:待发送数据的缓冲区首地址
- length buf:缓冲区中数据的长度
- flags:调用方式标志位
- dest_addr:指针,指向目的套接口的地址,内含地址和端口号信息;
- dest_len:所指地址的长度
- 返回值:成功则返回传送出去的字节数,失败返回-1;
例如:sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr))
接收函数:
ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);
- sockfd:套接口的描述符
- buf:接收数据缓冲区首地址
- len:缓冲区长度
- flags:调用操作方式。是一个或者多个标志的组合体,可通过|操作连在一起
- from:(可选)指针,指向装有源地址的缓冲区
- fromlen:(可选)指针,指向from缓冲区长度值
- 返回值:返回读入的字节数;
例如:recvfrom(sockfd, recvline, 1024, 0, NULL, NULL)
============================================
访问web服务器页面:
1)下载BOA,并在ubuntu下解压;www.boa.org;
2)进入src目录,运行.config,生成makefile文件,vi makefile,修改:
CC = gcc改为:CC = arm-none-linux-gnueabi-gcc -static
CPP = gcc -E改为:CPP = arm-none-linux-gnueabi-gcc -E -static
3)执行make命令,若出错修改compat.h:
#define TIMEZONE_OFFSET(foo) foo##->tm_gmtoff改为
#define TIMEZONE_OFFSET(foo) foo->tm_gmtoff
4)重新编译;
5)使用arm-none-linux-gnueabi-strip boa命令为boa文件瘦身;
6)使用cp命令拷贝编译最终生成的boa到NFS文件系统的bin目录下面;
7)在NFS文件系统下面建立几个文件夹,并修改若干配置文件,在自动运行脚本文件中添加自动启动命令“boa &”;
8)在www目录下,使用vi index.html命令建立index.html文件;
9)开发板是挂载NFS网络文件系统;
10)在pc机器打开网页浏览器,输入开发板的IP地址,可以看到在开发板上创建的index.html;
=======================================
通过web服务器网页进行控制led:
CGI(Common Gateway Interface)规范定义了web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等
1)修改网页源码:通过表单向服务器提交信息,在表单里面指定了服务器端处理接收信息的CGI程序;
2)根据boa的配置文件boa.conf里面指定的CGI程序存储目录,创建cgi程序;(*.c文件,获取web客户端提交过来的数据,然后对数据解析,
最后调用led的ioctl函数来点亮或关闭led)
3)编译*.c文件,生成*.cgi文件;(命令:arm-none-linux-gnueabi-gcc -o *.cgi *.c -static)
4)使用chmod 命令修改*.cgi文件的权限,变为可执行;(chmod 777 *.cgi)
5)通过PC机的浏览器访问开发板网页即可;(没看到控制结果)
CGI函数://该函数用来取得参数envvar环境变量的内容
char *getenv(char *envvar)
例如:getenv("QUERY_STRING")
QUERY_STRING:获取网页传过来的数据
========================================

posted @ 2021-12-11 16:40  随风而释  阅读(286)  评论(0)    收藏  举报