为什么移植操作系统? 1、软硬件的耦合度,提高然间的移植性(开发模式) 2、操作系统提供很多库和工具,提高开发效率 3、操作系统提供多任务机制 4、操作系统提供了丰富的网络协议栈,实现远程传输
嵌入式Linux多任务编程(多进程、多线程) 1、什么是多任务? ·单任务 和 多任务 单任务 一个任务执行之后,才可以执行下一个任务 一个任务在执行的过程中,不可以执行下一个任务,只有执行完之后,才可以执行下一个 多任务 在响应一个任务的时候,中途回去响应其他任务,之后再返回响应原本的任务 ·单核CPU 和 多核CPU 同时执行必须建立在多核的情况下 ·并发 和 并行 并发 多个任务,但是只能处理其中一个任务 并行 多个任务,可以同时响应多个任务,但是执行的时候可能不是并行 2、多任务操作的实现方式:进程、线程
一、进程的概念
程序与进程的差别 程序是“静态”的,进程是“动态”的 进程是暂时的,程序是永久的。 进程与程序的组成不同。 进程与程序的对应关系。 程序是被动的实体,如存储在磁盘上包含一系列指令的文件(经常被称为可执行文件)。 进程则是活动实体,具有一个程序计数器用于表示下个表示执行命令和一组相关资源。
Linux系统是一个多进程系统,具有并行性、互不干扰的特点。每个进程都是一个独立的运行单位,拥有各自的权利和责任。 其中每个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,他也不会影响系统中的其他进程。
缺点 自己独立的空间,安全多任务机制(互不干扰);开销很大(进程的创建、进程的切换)
进程的状态 新的:进程正在创建 运行:指令正在执行 等待:进程等待发生的某个事件(如I/O完成或收到信号) 就绪:进程等待分配处理器 种植:进程已经执行完毕
进程中每个进程表示,采用进程控制块(PCB),也称为任务控制模块。 包含很多相关信息 进程状态 程序计数器 CPU寄存器 CPU调度信息 内存管理信息 记账信息 I/O状态信息
获得pid pid给每个进程的选序号 创建一个task_struct结构体变量 getpid函数:获取调用该函数进程的进程pid getppid函数:获取调用该函数进程的父进程pid,第一个p是parent,第二个是process
Linux的进程表示 C语言结构的task_struct来表示(双向链表) 位于内核源代码目录内的头文件<linux/sched.h> 结构体里面包含:进程状态、调度、内存管理信息、打开文件列表、指向父进程的指针及指向子进程和兄弟进程列表的指针等。 父进程(parent process):创建它的进程 子进程(child process):为它自身创建的进程 兄弟进程(sibling process):为具有同一父进程的进程
二、进程的调度
通过ps命令 -A/-el:显示系统所有的进程(包括守护进程),相当于-e -x/-f:列出进程的详细信息 -H:显示进程树 -r:只显示正在运行的进程 top htop:显示资源占用
没有办法改变进程调度的策略-内核解决
进程的基本三态:就绪态、执行态、等待态(用户角度) 分为三态可以更好地进行调度策略
1、进程因为等待输入进入而堵塞 2、调度程序选择另一个进程 3、调度程序选择一个进程开始运行 4、出现有效的输入
调度策略 1、抢占式(设置优先级) 高优先级优先 2、非抢占式 时间片轮转(最重要) 先到先服务 短进程优先
细分:
ps -ax可以看到进程的状态 nice -n 5 top查看优先级 NI[-20,19]:nice值越小,抢占CPU能力越强,nice会影响进程的优先级 PRI[0,139]进程的优先级,也叫动态优先级,值越小,优先级越高 实时进程与非实时进程: 实时进程:优先级[0,99],采用实时进程的调度算法 非实时进程:优先级[100,139],采用01/CFS等调度方法
进程的分类 ·处理器消耗型 ·I/O消耗型
进程的同步 临界资源:操作系统中将一次只允许一个进程访问的资源称为临界资源,需要互斥访问。(强行多个进程操作,会导致文件内容的破坏,需要对进程进行排序,有进程访问的时候,其他进程不可以访问)
三、进程的创建
fork函数 头文件:<unistd.h> 函数原型:pid_t fork(void); 函数功能:从调用该函数的进程复制出子进程,被复制进程被称为父进程,复制出来的进程为子进程。 函数参数:无参数 函数返回值:fork调用一次,返回两次,可能三种不同的值。 父进程的fork,成功返回子进程的PID,失败返回-1,errno被设置。 子进程的fork,成功返回0,失败返回-1,errno被设置。
写时复制 当我对物理空间进行修改的时候,才算更改,否则就是共享资源 可以节省资源
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int count = 0;
__pid_t pid;
pid = fork();
fork();
count ++;
printf("count = %d\n",count);//出现两次,一次是在子进程,一次是在父进程
# if 0
if(pid < 0)
{
perror("fork error!\n");//创建失败
exit(1);
}
if(pid > 0)//父进程做什么与子进程没有关系
{
printf("pid = %d\n",pid);
printf("parent pid = %d\n",getpid());
printf("parent!\n");
printf("count = %d\n",count);
}
else if(pid == 0)
{
count ++;
printf("child pid = %d\n",getpid());//获取调用该函数进程的进程pid
printf("child ppid = %d\n",getppid());//获取调用该函数进程的父进程pid,第一个p是parent,第二个是process
printf("child!\n");
printf("count = %d\n",count);
}
#endif
return 0;
}
小测试-读写鼠标键盘,同时证明进程拥有自己的独立空间
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
__pid_t pid;
//int fd = open("a.txt",O_RDWR | O_CREAT | O_APPEND,0655);//进程空间共享
// if(fd == -1)
// {
// perror("open file error!");
// exit(1);
// }
//int fd = open;
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
if(pid > 0)
{
#if 0
int fd = open("dev/input/mouse",O_RDWR);
int cor;
while(1)
{
read(fd,&cor,sizeof(cor));
printf("cor = %d\n",cor);
sleep(3);
}
#endif
// while(1)
// {
int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
// }
}else if(pid == 0)
{
#if 0
char buffer[1024];
while(1)
{
memset(buffer, 0, sizeof(buffer));
int n_r = read(0,buffer,sizeof(buffer)-1);//实际读到的字节数
buffer[n_r] = '\0';
printf("buffer = %s\n",buffer);
}
#endif
// while(1)
// {
int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
write(fd,"hhh",3);
write(fd,"www",3);
write(fd,"\n",1);
// }
}
return 0;
}
四、进程的创建-exec()函数族
exec函数族 功能:执行一个二进制文件 头文件:<unistd.h> 命名规则: L:参数以列表的形式出现 V:参数以数组的形式出现 E:为新进程提供新的环境变量 P:在用户的绝对路径path下查找可执行文件,该文件必须在用户路径下,可以指定程序文件名 重点函数: int execl(const char *path,const char *arg,...); int execv(const char *path,char *const argv[],...); int execvpe(const char *file,char *const argv[],char *const envp[]);
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
__pid_t pid;
//int fd = open("a.txt",O_RDWR | O_CREAT | O_APPEND,0655);//进程空间共享
// if(fd == -1)
// {
// perror("open file error!");
// exit(1);
// }
//int fd = open;
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
if(pid > 0)
{
#if 0
int fd = open("dev/input/mouse",O_RDWR);
int cor;
while(1)
{
read(fd,&cor,sizeof(cor));
printf("cor = %d\n",cor);
sleep(3);
}
#endif
// while(1)
// {
// int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
// write(fd,"hello",5);
// write(fd,"world",5);
// write(fd,"\n",1);
// }
execlp("./write1","./write1",NULL);
}else if(pid == 0)
{
#if 0
char buffer[1024];
while(1)
{
memset(buffer, 0, sizeof(buffer));
int n_r = read(0,buffer,sizeof(buffer)-1);//实际读到的字节数
buffer[n_r] = '\0';
printf("buffer = %s\n",buffer);
}
#endif
// while(1)
// {
// int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
// write(fd,"hhh",3);
// write(fd,"www",3);
// write(fd,"\n",1);
// }
execlp("./write2","./write1",NULL);
}
return 0;
}
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
close(fd);
return 0;
}
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
write(fd,"hello",5);
write(fd,"world",5);
write(fd,"\n",1);
close(fd);
return 0;
}
五、进程创建(vfork\system)
vfork 头文件:#include <sys/types.h> #include <unsitd.h> 形式:pid_t vfork(void) 功能:创建子进程 对fork的改进 对fork的改进更为彻底、简单粗暴 vfork是为子进程立即执行exec的程序专门设计的 无需为子进程复制 虚拟内存页或页表,子进程直接共享父进程的资源 在子进程调用exec之前,将暂停父进程 使用fork的时候,父子进程是单独独立的,但是使用vfork后,两者的数据会互相产生影响
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
__pid_t pid;
int count = 0;
pid = vfork();
if(pid < 0)
{
perror("vfrok error!");
exit(1);
}
if(pid > 0)
{
for(int i = 0;i < 3;i++)
{
printf("Parent!\n");
printf("conut = %d\n",count);
sleep(1);//将主动权让出去
}
}
else if(pid == 0)
{
for(int i = 0;i < 3;i++)
{
sleep(2);
count++;
//printf("Child\n");
//sleep(1);
execl("./demo","./demo",NULL);
}
//只有调用,才不会出现段错误
//段错误是因为空间共享,空间会释放两次
//还有一种解除段错误的方法是添加exit(1);
//exit(1)是异常退出,不会释放空间,进程会让父进程释放
//当子进程使用exec时,最适合调用vfork
}
return 0;
}
system函数原型 头文件:#include <stdlib.h> int system(const char *commard); 功能:创建子进程,并加载新进程程序到子进程空间,运行起来。 参数:新程序的路径名
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
printf("hello\n");
//exe函数族
//execl会覆盖后面的代码
//execl("./demo","./demo","hello1","hello2",NULL);
//exev将命令封装进一个数组
// char *arg[] = {"./demo","hello1","hello2",NULL};
// execv("./demo",arg);
//execlp根据路径选择
//execlp("./demo","hello1","hello2",NULL);
char *env[]={"USER = admin","PASSWD = admin",NULL};
char *arg[]={"./demo","hello1","hello2",NULL};
//execvpe("./demo",arg,env);
//execl("./write2",NULL);
system("./demo hello1 hello2");
system("clear");
printf("welcome to jsetc\n");
return 0;
}
八、进程退出(exit VS _exit)
_exit 不刷缓冲区 执行流程: 关闭进程打开的文件描述符、释放该进程持有的文件锁 关闭该进程打开的信息量、消息队列 取消该进程通过mmap()创建的内存映射 将该进程的所有子进程交给Init托管 给父进程发送一个SIGCHLD信号(结束信号)
exit函数 POSIX标准和ANSIC定义的标准函数 头文件:#include <stdlib.h> 其实是对系统调用_exit的封装 函数原型:void exit(int status); 函数功能:终止当前进程 参数说明:用于识别进程的退出状态,shell或父进程可以获取该值 -0:表示进程可以正常退出 - -1/1:表示进程异常退出 - 2~n:表示用户可以自定义
echo $? 查看当前进程结束返回值
exit执行流程: 调用退出处理程序(通过atexit、on_exit注册的函数) 刷新stdio流缓冲区 使用由status提供的值执行_exit系统调用函数 关闭进程打开的文件描述符、释放进程持有的文件锁 关闭进程打开的信号量、消息队列 取消该进程通过mmap创建的内存映射
abort函数 头文件:#include <stdlib.h> 定义函数:void abort(void); 函数说明:abort()将引起进程异常的终止,此时所有已打开的文件流会自动关闭,所有的缓冲区数据会自动写回。 返回值:无
两者的区别: exit是库函数是对_exit系统调用的封装 调用_exit之前,他会执行各种动作 调用推出处理程序(通过atext和on_exit注册的回调函数) 刷新stdio流缓冲区 使用由status提供的值执行_exit系统调用
atexit/on_exit函数 退出处理程序 在exit推出后可以自动执行用户注册的退出处理程序 执行顺序预注册顺序相反 函数原型:int atexit(void(*function)(void)); 函数原型:int on_exit(void(8function)(int,void*),void *arg);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
void my_exit(void)
{
printf("my exit2\n");
}
void my_exit2(int ret,void *p)
{
printf("my exit2\n");
printf("ret = %d\n",ret);
printf("p = %s\n",(char*)p);
}
int main(int argc,char **argv,char **env)
{
//atexit(my_exit);
on_exit(my_exit2,"hello world");
printf("hello world!\n");
exit(1);
while(1);
return 0;
}
九、进程等待(僵尸进程、孤儿进程)
使用ps命令查看进程 -A/-e:显示系统所有的进程(包括守护进程),相当于-e -a:显示所有终端下的所有用户运行的进程 -u:显示用户名、CPU百分比和内存的使用 -x/-f:列出进程的详细信息 -H;显示进程树 -r:只显示正在运行的进程 -o:分类输出 $ ps -axf -o pid,pidd,state,tty,command 监视进程:$top
父进程运行结束时,子进程资源回收
僵尸进程: 子进程终止了,但是父进程还活着,子进程的资源没有被立即回收,子进程就是僵尸进程。 为什么子进程会变成僵尸进程? 子进程已经终止不再运行,但是父进程还在运行,它没有释放子进程所占有的资源,所以变成了僵尸进程。 孤儿进程: 子进程活着,父进程终止,子进程就是孤儿进程。 为了能够回收孤儿进程终止后的资源,孤儿进程会被托管给我们前面介绍的pid==1的init进程,每当托管的子进程终止的时候,init就会立即主动回收孤儿进程资源,回收资源的速度很快,所以孤儿进程没有机会变成僵尸进程。
进程PID == 0 作用: 调度进程,实现进程间的调度和切换,该进程根据调度算法,会让CPU轮换的执行所有的进程。 怎么实现? 当pc指向不同的进程时,cpu就去执行不同的进程,这样就可以实现切换。 怎么来的? 由OS演变而来,OS启动后,最后有一部分代码会持续的执行,这个就是PID==0的进程。 由于这个进程是OS的一部分,凡是由OS代码演变来的进程,都称之为系统进程。
进程PID==1 作用: 1)初始化: 被称为init进程,作用是,会读取各种各样的系统文件,使用文件中的数据来初始化OS的启动。 2)托管孤儿进程 3)原始父进程 原始进程---》进程---》进程---》终端进程---》a.out进程 怎么运行的? 这个进程不是OS演变而来的,不属于OS的代码,这个进程是一个独立的程序,程序代码放在了/sbin/init下,当OS启动起来之后,OS回去执行init程序,将他的我代码加载到内存,这个进程就运行起来了。
进程PID==2 作用: 页精灵进程,专门负责虚拟内存的请页操作。 疑问:什么是页精灵? 也称为守护进程。 当OS支持虚拟内存机制时,加载应用程序到内存时,并不会进行完整的代码拷贝,只会拷贝当前要运行的那部分代码,当这部分代码运行完毕后,会再拷贝另一部分需要运行的代码到内存中,拷贝时是按照一页一页来操作,每一页大概4096字节,这就是换页操作。
十、进程等待(wait)
wait函数-僵尸进程 函数原型:pid_t wait(int *status) 头文件:#include <sys/wait.h> 函数功能:等待子进程的终止及消息 参数说明:子进程调用exit/_exit时的status 返回值: Wait调用成功,会返回已终止子进程的pid Wait调用失败,返回-1,设置errno值 若子进程没有终止,wait调用会阻塞父进程,直到子进程终止,子进程终止后,该调用立即返回。
#include <stdio.h>
#include <sys/wait.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
__pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
if(pid == 0)
{
for(int i = 0;i < 3;i++)
{
printf("helloworld\n");
sleep(1);
}
exit(3);
}
else if(pid > 0)
{
printf("welcome to jsetc\n");
int ret;
wait(&ret);
int num = WEXITSTATUS(ret);//获取状态值
printf("child is exit! = %d\n",num);
}
return 0;
}
十一、进程等待(waitpid)
waitpid函数 函数原型: pid_t waitpid(pid_t pid,int *status,int options); 头文件: #include <sys/type.h> #include <sys/wait.h> 函数说明: waitpid()会暂停目前进程的执行,知道有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。紫禁城的状态会由参数status返回,而子进程的进程识别码也会一块返回。如果不在意结束状态值,则参数status可以设置为NULL。参数pid为欲等待的子进程识别码,其他数值意义如下: pid < -1:等待进程组识别码为pid绝对值的任何进程 pid = -1:等待任何子进程,相当于wait() pid = 0:等待进程组识别码与目前进程相同的任何子进程 pid > 0:等待任何子进程识别码为pid的子进程 参数Options可以为0或下面的OR组合: WNOHANG 如果没有任何已经结束的子进程则马上返回,不予等待 WUNTRACED 如果子进程进入暂停执行的情况则马上返回,但结束状态不予理会。 子进程的结束状态返回后存于status,底下有几个宏可判别结束情况: WIFEXITED(status)如果子进程正常结束则为非0值