由于父进程先于子进程退出,子进程就变为孤儿进程,并由 init 进程作为其父进程收养。
2、在子进程调用setsid()创建新会话;
在调用了 fork() 函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变。这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。
setsid()创建一个新会话,调用进程担任新会话的首进程,其作用有:
使当前进程脱离原会话的控制
使当前进程脱离原进程组的控制
使当前进程脱离原控制终端的控制
这样,当前进程才能实现真正意义上完全独立出来,摆脱其他进程的控制。
3、再次 fork() 一个子进程,父进程exit退出;
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端,可以通过 fork() 一个子进程,该子进程不是会话首进程,该进程将不能重新打开控制终端。退出父进程。
也就是说通过再次创建子进程结束当前进程,使进程不再是会话首进程来禁止进程重新打开控制终端。
4、在子进程中调用chdir()让根目录“/”成为子进程的工作目录;
这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir。(避免原父进程当前目录带来的一些麻烦)
5、在子进程中调用umask()重设文件权限掩码为0;
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(就是说可读可执行权限均变为7)。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此把文件权限掩码重设为0即清除掩码(权限为777),这样可以大大增强该守护进程的灵活性。通常的使用方法为umask(0)。(相当于把权限开发)
6、在子进程中close()不需要的文件描述符;
同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。其实在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。(关闭失去价值的输入、输出、报错等对应的文件描述符)
for (i=0; i < MAXFILE; i++)
close(i);
7、守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。
一张简单的图可以完美诠释之前几个步骤:
至此为止,一个简单的守护进程就建立起来了。
注意守护进程一般需要在 root 权限下运行。
通过
- ps -ef | grep ‘daemon’
对比执行前后确实可以看到多了一个进程:
并且产生了 daemon.log,里面是这样的时间标签
Thu Dec 8 14:35:11 2016
Thu Dec 8 14:36:11 2016
Thu Dec 8 14:37:11 2016
最后我们想退出守护进程,只需给守护进程发送 SIGQUIT 信号即可
- sudo kill -3 26454
再次使用 ps 会发现进程已经退出。
程序如下:
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
static bool flag = true;
void create\_daemon();
void handler(int);
int main()
{
time_t t;
int fd;
create\_daemon();
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL))
{
printf("sigaction error.\n");
exit(0);
}
while(flag)
{
fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd == -1)
{
printf("open error\n");
}
t = time(0);
char \*buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
void handler(int sig)
{
printf("I got a signal %d\nI'm quitting.\n", sig);
flag = false;
}
void create\_daemon()
{
pid_t pid;
/\*(1)-----创建一个进程来用作守护进程-----\*/
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
/\*(1.1)-----------原父进程退出-------------\*/
else if(pid)
{
exit(0);
}
/\*(2)---setsid使子进程独立。摆脱会话控制、摆脱原进程组控制、摆脱终端控制----\*/
if(-1 == setsid())
{
printf("setsid error\n");
exit(1);
}
/\*(3)---通过再次创建子进程结束当前进程,使进程不再是会话首进程来禁止进程重新打开控制终端----\*/
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid)
{
exit(0);
}
/\*(4)---子进程中调用chdir()让根目录成为子进程工作目录----\*/
chdir("/");
int i;
/\*(6)---关闭文件描述符(常说的输入,输出,报错3个文件)----\*/
for(i = 0; i < 3; ++i)
{
close(i);
}
/\*(5)---重设文件掩码为0(将权限全部开放)----\*/
umask(0);
return;
}
以下为其他的两种实现,大同小异:
C++实现 :
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t pid;
char buf[]="this is a dameon\n";
pid=fork();//创建一个进程用来做守护进程
if(pid>0){ //父进程退出,此时 子进程变为孤儿进程。
exit(0);
}
setsid(); //使子进程独立1.摆脱原会话控制 2.摆脱原进程组的控制 3.摆脱控制终端的控制
chdir("/"); //改变当前工作目录,这也是为了摆脱父进程的影响
umask(0); //重设文件权限掩码
int i;
for(i=0;i<1024;i++){ //关闭文件描述符(常说的输入,输出,报错3个文件),
close(i); //因为守护进程要失去了对所属的控制终端的联系,这三个文件要关闭
}
int fd=open("dameon.txt",O_CREAT|O_WRONLY|O_APPEND,0777);
if(fd<0){
perror("open");
return -1;
}
while(1){
write(fd,buf,strlen(buf)+1);
printf("This is a deamon!!\n");
sleep(5);
}
close(fd);
}
C语言实现(有时间再研究):
test.c文件
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void init\_daemon(void);//守护进程初始化函数
main()
{
FILE \*fp;
time_t t;
init\_daemon();//初始化为Daemon
while (1)//每隔一分钟向test.log报告运行状态
{
sleep(10);//睡眠一分钟
if ((fp = fopen("test.log", "a")) >= 0)
{
t = time(0);
fprintf(fp, "I'm here at %sn",asctime(localtime(&t)) );
fclose(fp);
}
}
}
init.c文件
#include<unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
void init\_daemon(void)
{
int pid;
int i;
if (pid = fork())
exit(0);//是父进程,结束父进程
else if (pid< 0)
exit(1);//fork失败,退出
//是第一子进程,后台继续执行
setsid();//第一子进程成为新的会话组长和进程组长
//并与控制终端分离
if (pid = fork())
exit(0);//是第一子进程,结束第一子进程
else if (pid< 0)
exit(1);//fork失败,退出
//是第二子进程,继续
//第二子进程不再是会话组长
for (i = 0; i< NOFILE; ++i)//关闭打开的文件描述符
close(i);
chdir("/tmp");//改变工作目录到/tmp
umask(0);//重设文件创建掩模
return;
}
编译:gcc –g –o test init.c test.c
执行:./test
查看进程:ps –ef
从输出可以发现test守护进程的各种特性满足上面的要求。
6、网络的Socket交互过程
此问题是网络编程
1)、什么是网络编程
网络编程就是编写程序使两台连联网的计算机相互交换数据。怎么交换数据呢?两台电脑都插一根线就可以了吗?稍微夸张点说差不多是这个意思(需要物理连接)
有人就会问了,我平常跟张三聊QQ的时候我也没跟他直接连网线那怎么可以联网呢。你买了电信的宽带电信是不是得给你牵线装猫,最终这些线会连到电信的机房由他们来管理。在这个基础上,
如何编写数据传输软件呢。操作系统提供了“套接字”(socket)的组件我们基于这个组件进行网络通信开发。
本章主要讨论tcp套接字,接下来的工作流程都会以“打电话”来生动表达,tcp套接字可以比喻成电话。
电话可以同时用来拨打和接听的,但对套接字而言,拨打和接听是有区别的。我们先讲解套接字创建过程。其实这个过程跟我们生活中的打电话的场景比较相似。
我们来解析打电话的步骤:
1.通信方式有很多种,可以当面沟通、书信沟通、电话沟通、托人带话等等。
这里张三和李四约定好都用电话沟通(确认通讯协议,这里指TCP/IP),张三给李四打电话(张三在这里的身份是客户端,而李四对应身份是服务端 身份也确认好了)。
2.双方打电话得有电话机(创建socket对象)
3.张三必须知道拨打对象的电话号码(知道服务端的ip和port),李四电话号码是123456(绑定套接字)
4.张三拨打李四电话(客户端连接服务端,connect连接)
5.被打电话的那一方听到电话响了(listen监听)
6.李四害怕是推销电话想着要跟他确认身份是不是张三,不是张三就准备挂断电话
7.接起电话确认对方身份,张三问是李四吗(三次握手中,第一次握手)
8.李四回答,我是李四。你是?(三次握手,第二次握手)
9.张三说你好李四,我是张三(三次握手,第三次握手)
10.确认过眼神遇上对的人,李四决定跟他继续谈话(accept接受连接请求)
11.接下来就开始长篇大论的攀谈(数据交互)
12.最终要挂电话了,张三对李四说那今天就讲到这里(四次挥手,第一次)
13.李四说行啊今天就讲到这里(四次挥手,第二次)
14.张三说那我挂断了啊(四次挥手,第三次)
15.李四说好的你挂吧(四次挥手,第四次)
16.挂断(结束)
阅读以上流程接下来我们来看看流程图就非常好理解了
或者我阅读及学习过程中找到另外一个博客客友的一个解释:可参考:TCP/IP的TCP通信过程
7、TCP 三次握手和终止连接的4次握手过程
此问题再第六个问题中已进行阐述!也可在此处进行简单的说明:
1)、三次握手在这里插入图片描述
①TCP是一种精致的,可靠的字节流协议。
②在TCP编程中,三路握手一般由客户端(Client)调用Connent函数发起。
③TCP3次握手后数据收发通道即打开(即建立了连接)。
④简述三路握手过程:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
2)四次挥手
所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
8、介绍一下你做过的XX项目(简历上的)?您在里面负责哪一块?
9、你用的是什么CPU?是什么样的内核?
10、ARM有几种CPU模式,分别是什么?
ARM体系的CPU有以下7种工作模式:
1、用户模式(usr):正常的程序执行状态
2、快速中断模式(fiq): 处理快速中断,支持高速数据传送或通道处理
3、中断模式(irq): 处理普通中断
4、管理模式(svc):操作系统使用的保护模式
5、系统模式(sys):运行具有特权的操作系统任务
6、数据访问终止模式(abt):数据或指令预取终止时进入该模式
7、未定义指令终止模式(und):未定义的指令执行时进入该模式
11、列举几种文件系统,分别说说他们的优缺点。
Linux支持多种文件系统,包括ext2、ext3、vfat、ntfs、iso9660、jffs、romfs和nfs等,为了对各类文件系统 进行统一管理,Linux引入了虚拟文件系统VFS(Virtual File System),为各类文件系统提供一个统一的操作界面和应用编程接口。
Jffs2(journaling flash filesystem V2),
主要用于NORflash,基于MTD驱动层,特点:可读写、支持数据压缩,基于哈希表的日志型文件系统,提供掉电保护。缺点是文件系统已满或接近满时,因为垃圾收集的原因运行速度放慢。
Yaffs :yet another flash file system
用于NAND flash,不支持数据压缩,速度快,yaffs2支持2kB的大页nand flash 。
Cramfs:只读文件系统,基于MTD驱动,压缩比高达2:1,运行时解压缩到Ram中
Romfs:只读的,按顺序存放数据。Uclinux常用的文件系统
Ramdisk:将一部分固定大小的内存当做分区来使用,可作为根文件系统。
NFS:network file system
在开发阶段,可以在主机上建立根文件系统用于调试,挂载到嵌入式设备,可以很方便修改根文件系统的内容
12、按键处理用了CPU哪个中断?
请参考:按键和CPU的中断系统
13、嵌入式LINUX 2.6和2.4有什么区别?
嵌入式LINUX 2.6和2.4有什么区别?
这个问题涉及的面非常广泛,我们只能列出基本部分:
每个内核主要的变化在lwn.net/Articles/2.… 下面列举的是比较基础和必须的部分
1、使用新的入口必须包含
<linux/init.h>
module_init(your_init_func);
module_exit(your_exit_func);
老版本:int init_module(void);
void cleanup_module(voi);
2.4中两种都可以用,对如后面的入口函数不必要显示包含任何头文件。
2、模块参数必须显式包含<linux/moduleparam.h>
module_param(name, type, perm);
module_param_named(name, value, type, perm);
参数定义
module_param_string(name, string, len,perm);
module_param_array(name, type, num, perm);
老版本:
MODULE_PARM(variable,type);
MODULE_PARM_DESC(variable,type);
3、 模块别名MODULE_ALIAS(“alias-name”);
这是新增的,在老版本中需在/etc/modules.conf配置,现在在代码中就可以实现。
4、 模块计数int try_module_get(&module);module_put();
老版本:MOD_INC_USE_COUNT 和 MOD_DEC_USE_COUNT
5、 符号导出
只有显示的导出符号才能被其他模块使用,默认不导出所有的符号,不必使用EXPORT_NO,_SYMBOLS
老板本:默认导出所有的符号,除非使用EXPORT_NO_SYMBOLS
6、 设备号
kdev_t被废除不可用,新的dev_t拓展到了32位,12位主设备号,20位次设备号。
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
老版本:
8位主设备号,8位次设备号
int MAJOR(kdev_t dev);
int MINOR(kdev_t dev);
7、 内存分配头文件变更所有的内存分配函数包含在头文件<linux/slab.h>,而原来的<linux/malloc.h>不存在
老版本:内存分配函数包含在头文件<linux/malloc.h>
8、 结构体的初试化
gcc开始采用ANSI C的struct结构体的初始化形式:
static struct some_structure = {.field1 = value,.field2 = value,…};
老版本:非标准的初试化形式static struct some_structure = {field1: value,field2: value,…};
9、 request_module()
request_module(“foo-device-%d”, number);
老版本:
char module_name[32];
printf(module_name, “foo-device-%d”, number);
request_module(module_name);
10、 dev_t引发的字符设备的变化
- 1、取主次设备号为unsigned iminor(struct inode *inode);
unsigned imajor(struct inode *inode); - 2、老的register_chrdev()用法没变,保持向后兼容,但不能访问设备号大于256的设备。
- 3、新的接口为
a)注册字符设备范围
int register_chrdev_region(dev_t from, unsigned count, char *name);
b)动态申请主设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char *name);
看了这两个函数郁闷吧_!怎么和file_operations结构联系起来啊?别急!
c)包含 <linux/cdev.h>,利用struct cdev和file_operations连接
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *cdev, struct file_operations *fops);
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
(分别为,申请cdev结构,和fops连接,将设备加入到系统中!好复杂啊!)
d)void cdev_del(struct cdev *cdev);
只有在cdev_add执行成功才可运行。
e)辅助函数
kobject_put(&cdev->kobj);struct kobject *cdev_get(struct cdev *cdev);
void cdev_put(struct cdev *cdev);
这一部分变化和新增的/sys/dev有一定的关联。
11、 新增对/proc的访问操作<linux/seq_file.h>
以前的/proc中只能得到string, seq_file操作能得到如long等多种数据。
相关函数:
static struct seq_operations 必须实现这个类似file_operations得数据中得各个成员函数。seq_printf();
int seq_putc(struct seq_file *m, char c);
int seq_puts(struct seq_file *m, const char *s);
int seq_escape(struct seq_file *m, const char *s, const char *esc);
int seq_path(struct seq_file *m, struct vfsmount *mnt,
struct dentry *dentry, char *esc);
seq_open(file, &ct_seq_ops);等等
12、 底层内存分配
1)、<linux/malloc.h>头文件改为<linux/slab.h>
2)、分配标志GFP_BUFFER被取消,取而代之的是GFP_NOIO 和 GFP_NOFS
3)、新增__GFP_REPEAT,__GFP_NOFAIL,__GFP_NORETRY分配标志
4)、页面分配函数alloc_pages(),get_free_page()被包含在<linux/gfp.h>中
5)、对NUMA系统新增了几个函数:a) struct page *alloc_pages_node(int node_id,unsigned int gfp_mask,unsigned int order);b) void free_hot_page(struct page *page);c) void free_cold_page(struct page *page);
13、 内核时间变化
1)、现在的各个平台的HZ为
Alpha: 1024/1200; ARM: 100/128/200/1000; CRIS: 100; i386: 1000; IA-64:
1024; M68K: 100; M68K-nommu: 50-1000; MIPS: 100/128/1000; MIPS64: 100;
PA-RISC: 100/1000; PowerPC32: 100; PowerPC64: 1000; S/390: 100; SPARC32:
100; SPARC64: 100; SuperH: 100/1000; UML: 100; v850: 24-100; x86-64: 1000.
2)、由于HZ的变化,原来的jiffies计数器很快就溢出了,引入了新的计数器jiffies_64
3)、#include <linux/jiffies.h>
u64 my_time = get_jiffies_64();
4)、新的时间结构增加了纳秒成员变量struct timespec current_kernel_time(void);
5)、他的timer函数没变,新增
void add_timer_on(struct timer_list *timer, int cpu);
6)、新增纳秒级延时函数ndelay();
7)、POSIX clocks 参考kernel/posix-timers.c
14、 工作队列(workqueue)
1)、任务队列(task queue )接口函数都被取消,新增了workqueue接口函数
struct workqueue_struct *create_workqueue(const char *name);
DECLARE_WORK(name, void (*function)(void *), void *data);
INIT_WORK(struct work_struct *work,void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work,void (*function)(void *), void *data);
2)、申明struct work_struct结构
int queue_work(struct workqueue_struct *queue,struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue,struct work_struct *work,unsigned long delay);
int cancel_delayed_work(struct work_struct *work);
void flush_workqueue(struct workqueue_struct *queue);
void destroy_workqueue(struct workqueue_struct *queue);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
15、 DMA的变化
未变化的有:
void *pci_alloc_consistent(struct pci_dev *dev, size_t size,dma_addr_t *dma_handle);
void pci_free_consistent(struct pci_dev *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle);
变化的有:
1)、 void *dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, int flag);
void dma_free_coherent(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle);
2)、列举了映射方向:enum dma_data_direction {DMA_BIDIRECTIONAL = 0,DMA_TO_DEVICE = 1,DMA_FROM_DEVICE = 2,DMA_NONE = 3,};
16、 新增完成事件(completion events)<linux/completion.h>init_completion(&my_comp);
void wait_for_completion(struct completion *comp);
void complete(struct completion *comp);void complete_all(structcompletion *comp);
RCU(Read-copy-update)
rcu_read_lock();
17、 中断处理
1)、中断处理有返回值了。
IRQ_RETVAL(handled);
2)、cli(), sti(), save_flags(), 和 restore_flags()不再有效,应该使用local_save_flags() 或local_irq_disable()。
3)、synchronize_irq()函数有改动
4)、新增int can_request_irq(unsigned int irq, unsigned long flags);
5)、 request_irq() 和free_irq() 从 <linux/sched.h>改到了 <linux/interrupt.h>
18、 异步I/O(AIO)<linux/aio.h>ssize_t (*aio_read) (struct kiocb *iocb, char __user *buffer,size_t count, loff_t pos);
ssize_t (*aio_write) (struct kiocb *iocb, const char __user *buffer,size_t count, loff_t pos);
int (*aio_fsync) (struct kiocb *, int datasync);
新增到了file_operation结构中。
is_sync_kiocb(struct kiocb *iocb);
int aio_complete(struct kiocb *iocb, long res, long res2);
19、 block I/O 层这一部分做的改动最大。不祥叙。
20、内核的一些功能函数的名称、参数、头文件、宏定义的变化
如:中断注册函数的格式及参数在2.4内核、2.6内核低版本和高版本之间都存在差别
在2.6.8中,中断注册函数的定义为:
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),unsigned long irq_flags, const char * devname, void *dev_id)
irq_flags的取值主要为下面的某一种或组合:
SA_INTERRUPT、SA_SAMPLE_RANDOM、SA_SHIRQ
在2.6.26中,中断注册函数的定义为:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)typedef irqreturn_t (*irq_handler_t)(int, void *);
irq_flags的取值主要为下面的某一种或组合:(功能和2.6.8的对应)
IRQF_DISABLED、IRQF_SAMPLE_RANDOM、IRQF_SHARED
平台代码关于硬件操作方面封装的一些函数的变化
内 核中,硬件平台相关的代码在内核更新过程中变化比较频繁。和我们的设备驱动也是息息相关。所以在针对一个新内核编写设备驱动前,一定要熟悉你的平台代码的 结构。有时平台虽然提供了内核要求的接口函数,但使用起来功能却并不完善。下面还是先举个例子说明平台代码更新对设备驱动的影响。
如: 在linux-2.6.8内核中,调用set_irq_type(IRQ_EINT0, IRQT_FALLING);去设置S3C2410的IRQ_EINT0的中断触发信号类型,你会发现不会有什么效果。跟踪代码发现内核的 set_irq_type函数需要平台提供一个针对硬件平台的实现函数
static struct irqchip s3c_irqext_chip = {
.mask = s3c_irqext_mask, .unmask = s3c_irqext_unmask, .ack = s3c_irqext_ack, .type = s3c_irqext_type};
s3c_irqext_type就是linux内核需要的实现函数,而s3c_irqext_type在2.6.8中的实现为:
static int s3c_irqext_type(unsigned int irq, unsigned int type)
{
irqdbf(“s3c_irqext_type: called for irq %d, type %d\n”, irq, type);
return 0;
}
原来并没有实现。而在较高版本的内核,如2.6.26内核中,这个函数是实现了的。
14、Uboot的作用和功能
1)、uboot是用来干什么的,有什么作用?
uboot 属于bootloader的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存中,启动内核
所以,由上面描述的,就知道,UBOOT需要具有读写flash的能力。
2)、uboot是怎样引导启动内核的?
uboot刚开始被放到flash中,板子上电后,会自动把其中的一部分代码拷到内存中执行,这部分代码负责把剩余的uboot代码拷到内存中,然后uboot代码再把kernel部分代码也拷到内存中,并且启动,内核启动后,挂着根文件系统,执行应用程序。
3)、uboot启动的大过程是怎么样的?
uboot启动主要分为两个阶段,主要在start.s文件中,第一阶段主要做的是硬件的初始化,包括,设置处理器模式为SVC模式,关闭看门狗,屏蔽中断,初始化sdram,设置栈,设置时钟,从flash拷贝代码到内存,清除bss段等,bss段是用来存储静态变量,全局变量的,然后程序跳转到start_arm_boot函数,宣告第一阶段的结束。
第二阶段比较复杂,做的工作主要是1.从flash中读出内核。2.启动内核。start_arm_boot的主要流程为,设置机器id,初始化flash,然后进入main_loop,等待uboot命令,uboot要启动内核,主要经过两个函数,第一个是s=getenv(“bootcmd”),第二个是run_command(s…),所以要启动内核,需要根据bootcmd环境变量的内容启动,bootcmd环境变量一般指示了从某个flash地址读取内核到启动的内存地址,然后启动,bootm。
uboot启动的内核为uImage,这种格式的内核是由两部分组成:真正的内核和内核头部组成,头部中包括内核中的一些信息,比如内核的加载地址,入口地址。
uboot在接受到启动命令后,要做的主要是:
1,读取内核头部;
2,移动内核到合适的加载地址;
3,启动内核,执行do_bootm_linux;
do_bootm_linux主要做的为:
1,设置启动参数,在特定的地址,保存启动参数,函数分别为setup_start_tag,setup_memory_tag,setup_commandline_tag,setup_end_tag,根据名字我们就知道具体的段内存储的信息,memory中为板子的内存大小信息,commandline为命令行信息,
2,跳到入口地址,启动内核
启动的函数为the_kernel(0,bd->bi_arch_number,bd->bi_boot_param)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新