您的位置:首页 > 运维架构 > Linux

Linux高级编程基础知识点

2016-06-11 22:58 459 查看
内核移植又名最小系统移植,移植后的内核具备网络通,串口通,根文件系统可用等基本条件。

将交叉编译工具arm-linux-gcc-4.3.2.tar.gz解压到/目录下的命令是:tar xvzf arm-linux-gcc-4.3.2.tar.gz –C /

嵌入式Linux系统由内核根文件系统两部分构成,两者缺一不可。

配置arm平台内核的命令:make menuconfig

Linux引入虚拟文件系统VFS(Virtual File System)为各类文件系统提供一个统一的应用编程接口

在嵌入式Linux应用中,主要的存储设备为RAMFLASH。Flash(闪存)作为嵌入式系统的主要存储媒介,主要有NOR

NAND两种技术。

Flash:存储器的擦写次数是有限的,NAND闪存还有特殊的硬件接口和读写时序。因此,必须针对Flash的硬件特性设计符合应用要求的文件系统。

Jffs2主要用于NOR型flash,基于MTD(memory technology device驱动层,特点是:可读写、支持数据压缩的日志型文件系统,并提供了崩溃/掉电安全保护等。

yaffs/yaffs2 (Yet Another Flash FileSystem)是专为NAND型flash而设计的一种日志型文件系统。

Cramfs是Linux的创始人 Linus 参与开发的一种只读的压缩文件系统,它也基于MTD驱动程序。Cramfs文件系统以压缩方式存储,在运行时解压缩,所有的应用程序要求被拷到RAM里去运行。

在嵌入式系统中,通常没有像BIOS那样的固件程序,因此整个系统的加载启动任务就完全由BootLoader来完成。

简述bootloader的定义.为什么需要进行bootloader移植?

BootLoader就是在操作系统运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统做好准备。 种不同的CPU体系结构都有不同的BootLoader。除了依赖于CPU的体系结构外,BootLoader 还依赖于具体的嵌入式板级设备的配置,比如板卡的硬件地址分配,外设芯片的类型等。这也就是说,对于两块不同的开发板而言,即使它们是基于同一种CPU而构建的,但如果他们的硬件资源或配置不一致的话,要想在一块开发板上运行的BootLoader程序也能在另一块板子上运行,还是需要作修改。

BootLoader 大多采用两阶段,分别完成什么工作?

BootLoader 的 stage1 通常包括以下步骤:

•硬件设备初始化

•为加载 BootLoader 的 stage2 准备 RAM 空间

•拷贝 BootLoader 的 stage2 到 RAM 空间中

•设置好堆栈

•跳转到 stage2 的 C 入口点。

== BootLoader 的 stage2 通常包括以下步骤:

•初始化本阶段要使用到的硬件设备;

•调用内核。

Linux由用户空间和内核空间两部分组成。内核空间与用户空间是程序执行的两种不同状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移。

SCI层为用户空间提供了一套标准的系统调用函数来访问Linux内核,搭起了用户空间到内核空间的桥梁。

进程管理的重点是创建进程(fork、exec),停止进程(kill、exit),并控制它们之间的通信(signal 或者 POSIX 机制)。进程管理还包括控制活动进程如何共享CPU,即进程调度。

内存管理的主要作用是控制多个进程安全地共享内存区域。

VFS隐藏各种文件系统的具体细节,为文件操作提供统一的接口。

Linux 内核中有大量代码都在设备驱动程序中,它们控制特定的硬件设备

关于内核配置.make config:基于文本模式的交互式配置。

make menuconfig:基于文本模式的菜单型配置。

make oldconfig:使用已有的配置文件(.config),但是会询问新增的配置选项。

make xconfig:图形化的配置(需安装图形化系统)。

内核模块

内核模块本身并被编译进内核文件(zImage或者bzImage)

模块加载函数通过module_init宏来指定.

模块卸载函数通过module_exit宏来指定,

加载模块的命令是insmod,卸载模块的命令是rmmod,显示模块的命令是lsmod。

内核模块有什么特点?

内核模块具有如下特点: 模块本身并不被编译进内核文件(zImage或者bzImage);

可以根据需求,在内核运行期间动态的安装或卸载。

内核模块对比应用程序,内核模块具有以下不同:

应用程序是从头(main)到尾执行任务,执行结束后从内存中消失。

内核模块则是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,直到卸载函数被调用,模块才从内核中消失。

在Linux系统中,所有打开的文件都对应一个文件描述符。文件描述符的本质是一个非负整数

由于lseek函数的返回值为文件指针相对于文件头的位置,因此下面调用的返回值就是文件的长度: lseek(fd, 0, SEEK_END)

下面那些函数是库函数fopen() fread() fwrite() fgetc() fputc(),哪些是系统函数creat(),open(),read(),write()lseek(),access()函数

关于下列函数说size_t fwrite (const void ptr, size_t size, size_t n,FILE *stream)从*缓冲区**ptr所指的数组中把n个字段写到stream指向的文件中,每个字段长为size个字节,返回实际写入的字段数。

size_t fread(void *ptr, size_t size, size_t n, FILE*stream)功能: 从stream指向的文件中读取n个字段,每个字段为size字节,并将读取的数据放入ptr所指的字符数组中,返回实际已读取的字节数。

char *getcwd(char *buffer,size_t size)可以得到当前路径。

int access(const char*pathname,int mode) 判断文件是否可以进行某种操作(读,写等)

进程是一个具有一定独立功能的程序的一次运行活动。他具有动态性、并发性、独立性异步性等特点。

进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。

一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。

死锁多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程都将永远不能再向前推进。

写出下列程序的运行结果,并分析。

#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t pid;

int count=0;
pid = fork();
count++;
printf( “count = %d\n", count );
return 0;
}

输出:
count = 1
count = 1


子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而不是共享。在子进程中对count进行加1的操作,并没有影响到父进程中的count值,父进程中的count值仍然为0。

写出下面程序的结果并分析。若将exit()换为_exit()j结果如何,并分析。

#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Using exit...\n");
printf("This is the content in buffer");
exit(0);
}
[root@(none) 1]# ./exit
Using exit...
//This is the content in buffer


由于 printf 函数使用的是缓冲 I/O 方式,该函数在遇到“\n”换行符时自动从缓冲区中将记录读出。

_exit()函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构;

exit()函数与_exit()函数最大的区别就在于 exit()函数在调用 exit 系统之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理 I/O 缓冲”一项。

关于进程间通信

管道是 Linux 中进程间通信的一种方式。这里所说的管道主要指无名管道,它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等.数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。

假设进程mysignal的进程号为2550,怎样通过命令向该进程发送一SIGINT信号,写出整个编译运行过程:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n ");

/*注册信号处理函数*/
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
exit(0);
}
 


关于信号

发送信号的函数主要有 kill()、raise()、alarm()以及 pause() .

Kill既可以向自身发送信号,也可以向其他进程发送信号,与kill函数不同的是,

raise函数是向进程自身发送信号。

alarm 也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送 SIGALARM 信号。要注意的是,一个进程只能有一个闹钟时间。当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

关于消息队列

消息队列就是一个消息的列表。用户可以从消息队列种添加消息、读取消息等。

消息队列中创建或打开消息队列使用的函数是 msgget,这里创建的消息队列的数量会受到系统消息队列数量的限制;

添加消息使用的函数是 msgsnd 函数,它把消息添加到已打开的消息队列末尾;

读取消息使用的函数是 msgrcv,它把消息从消息队列中取走,与 FIFO不同的是,这里可以指定取走某一条消息;

关于多线程 编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a

因为pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread。.线程自己调用pthread_exit函数正常退出。线程的正常退出方式包括线程从启动例程中返回。

有了进程为什么还要引入多线程?

和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。

程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:

使多CPU系统更加有效。操作系统会保证当线程 数不大于CPU数目时,不同的线程运行于不同的 CPU上。

改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

网络编程:

TCP/IP 协议模型从一开始就遵循简单明确的设计思路,

简化为 4 层:网络接口层、网络层、传输层、应用层。

UDP 提供不可靠的非连接型传输层服务,它允许在源和目的地之间传送数据,而不必在传送数据之前建立对话。

TCP是重要的传输层协议,目的是允许数据同网络上的其他节点进行可靠的交换。它能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输。TCP 协议具有严格的内装差错检验算法确保数据的完整性。

UDP也是传输层协议,它是无连接的,不可靠的传输服务。当接收数据时它不向发送方提供确认信息,它不提供输入包的顺序,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。由于它执行功能时具有较低的开销,因而执行速度比TCP快。

Linux中的网络编程通过Socket(套接字)接口实现,Socket是一种文件描述符。 流式套接字(SOCK_STREAM) 流式的套接字可以提供可靠的、面向连 接的通讯流。它使用了TCP协议。

TCP 保证了数据传输的正确性和顺序性。数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错,它使用数据报协议UDP.

原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等。

tcp服务器和客户端的编程步骤

基于TCP-服务器

创建一个socket,用函数socket();

绑定IP地址、端口等信息到socket上,用函数bind()

设置允许的最大连接数,用函数listen()

接收客户端上来的连接,用函数accept()

收发数据,用函数send()和recv(),或者 read()和write() 6.关闭网络连接

基于TCP-客户端


创建一个socket,用函数socket()

设置要连接的对方的IP地址和端口等属性

连接服务器,用函数connect()

收发数据,用函数send()和recv(),或者 read()和write()

关闭网络连接

简述udp服务器和客户端的编程步骤:

基于UDP-服务器

创建一个socket,用函数socket()

绑定IP地址、端口等信息到socket上, 用函数bind()

循环接收数据,用函数recvfrom()

关闭网络连接

基于UDP-客户端

创建一个socket,用函数socket()

绑定IP地址、端口等信息到socket上, 用函数bind()

设置对方的IP地址和端口等属性

发送数据,用函数sendto()

关闭网络连接

循环服务器:服务器在同一个时刻只可以响应一个客户端的请求;

并发服务器:服务器在同一个时刻可以响应多个客户端的请求;

TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求。因为UDP是非面向连接的,没有一个客户端可以老是占住服务端, 服务器对于每一个客户机的请求总是能够满足。

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout); Maxfd: 文件描述符的范围,比待检的最大文件描述符大1;Readfds:被读监控的文件描述符集; Writefds:被写监控的文件描述符集; Exceptfds:被异常监控的文件描述符集; Timeout:定时器

设备驱动

Linux用户程序通过设备文件来使用驱动程序操作字符设备和块设备。

主设备号用来标示与设备文件相连的驱动程序;

次设备号被驱动程序用来辨别操作的是哪个设备。

查看设备名、设备号,cat /proc/devices

手工创建设备文件 mknod /dev/设备文件名 设备类型 主设备号 此设备号;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: