嵌入式开发为什么要移植操作系统?
1.减小软硬件的耦合度,提高软件的移植性
2. 操作系统提供很多库和工具(QT Open CV),提高开发效率
3.操作系统提供多任务机制,______________________? (提高CPU的效率)
4.操作系统提供了丰富的网络协议栈,实现远程传输
Linux C 多进程编程(多进程、多线程)
1.什么是多任务
单任务————多任务
并发————并行
单核CPU ————多核CPU
2.多任务操作的实现方式
进程和线程
程序:
是一组指令和数据的集合,是静态的、存储在磁盘或其他存储介质上的文件。
程序可以被看作是一段代码的集合,描述了在计算机上执行的任务。
程序本身是静态的,需要加载到内存中才能执行。
进程:
是程序的抽象
是动态的
每个进程都都有独立的运行空间(虚拟地址空间),
每个进程都是一个独立的运行单位,拥有各自的权力和责任;(互不干扰)
进程是 安全的任务机制
缺点:开销大(进程创建和进程切换)
进程PID编号
父进程创建子进程
获取进程PID
获取父进程PID
ps命令、top命令和htop命令
在许多个已经处于就绪态的进程中,选择决定哪个进程进行调度(基于进程三态)
进程状态:
就绪态、执行态和等待态(阻塞态)
操作系统的核心就是任务(进程)管理
主要分为两大类:抢占式(设置优先级)和非抢占式(不设置优先级)
有如下策略
1.先到先服务; 2.短进程优先; 3.时间片轮转(使用最多); 4.高优先级优先
实时操作系统是一种响应速度快,准确性高(抢占式)
不同任务之间通过双向链表链接
进程分类:
处理器消耗型
渴望获取更多的CPU时间,并消耗掉调度器分配的全部时间片·常见例子:无限死循环、科学计算、影视特效渲染
I/O消耗型
由于等待某种资源通常处于阻塞状态,不需要较长的时间片 常见例子:等待用户输入、GUI程序、文件读写I/O程序
进程同步
多个进程访问同一个文件时;需要互斥访问,否则易产生错误;
操作系统把一次只允许一个进程访问的资源成为临界资源,需要互斥访问
作业:利用多进程实现,分别从键盘和鼠标读数据:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int count = 0;
pid_t pid = fork();
if (pid < 0) // 错误处理
{
perror("pid error");
exit(-1);
}
if (pid > 0) // 父进程执行鼠标读操作
{
int fd1 = open("/dev/input/mouse0", O_RDWR);
if (fd1 == -1)
{
perror("fd1 error");
exit(-1);
}
int location = 0;
while (1)
{
int r_num1 = read(fd1, &location, sizeof(int));
if (r_num1 > 0)
{
printf("mouse loaction=%d\n", location);
}
}
}
if (pid == 0) // 子进程执行键盘读写操作
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
while (1)
{
int r_num = (read(0, buffer, sizeof(buffer) - 1));
if (r_num > 0)
{
buffer[r_num] = '\0';
printf("%s\n", buffer);
}
memset(buffer, 0, sizeof(buffer));
}
}
return 0;
}
通过多进程实现父子进程对同一个文件进行写操作
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int count = 0;
int fd = open("a.txt", O_RDWR | O_APPEND | O_CREAT, 0777);
if (fd == -1)
{
perror("fd error");
exit(-1);
}
pid_t pid = fork();
if (pid < 0) // 错误处理
{
perror("pid error");
exit(-1);
}
if (pid > 0) // 父进程操作
{
write(fd, "hello", 5);
write(fd, "world", 5);
write(fd, "\n", 1);
}
if (pid == 0) // 子进程执行操作
{
write(fd, "FFFFF", 5);
write(fd, "KKKKK", 5);
write(fd, "\n", 1);
}
return 0;
}
父子进程的运行顺序,暂时是不需要明白;内部有进程调度算法
使用execl函数时,原函数在execl函数后的代码段会不起作用
表头文件:#include
1. int execl c const char *path,const char *arg,...)
函数说明:
execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
返回值﹔如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中
2. int execv ( const char *path,char *const argv[]);
函数说明:
execv ()用来执行参数path字符串所代表的文件路径,与execl ()不同的地方在于execve ()只需两个参数,第二个参数系利用指针数组来传递给执行文件。
返回值﹔如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
3. int execlp (const char*file,const char *arg,...);
函数说明: execlp ( )会从 PATH环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当作该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
返回值﹔如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
4.int execvp ( const char *file,char *const argv[]);
函数说明: execvp ( )会从 PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
错误代码:请参考execve ()
5.int execve ( const char *filename,char *const argv [].char *const envp[]);
函数说明:execve ()用来执行参数filename字符串所代表的文件路径,第二个参数系利用指针数组来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。
返国值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno
中。
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
// 1.参数表直接写入, 以NULL结尾
execl("./write1", "./write1", "hello", "world", NULL);
// 2.参数表用指针数组的形式, 以NULL结尾
char *arg1[] = {"./write2", "welcome", "zhaodeming", NULL};
xecv("./write2", arg1);
// 3.
execlp("/home/zdm/241/PROCESS_CODE/write1", "./write1", "hello", "world", NULL);
//execlp("./write1", "./write1", "hello", "world", NULL); // 也可以
// 4.
char *arg2[] = {"./write2", "welcome", "zhaodmeing", NULL};
execvp("/home/zdm/241/PROCESS_CODE/write2", arg2);
// 5. e---环境变量
char *env[] = {"USR=admin", "PASSWD=12345"};
execve("/home/zdm/241/PROCESS_CODE/write2", arg2, env);
printf("exce demo ok\n");
}
每个调用exec函数,会覆盖掉后面的代码
常常与fork函数联用:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int count = 0;
pid_t pid = fork();
if (pid < 0) // 错误处理
{
perror("pid error");
exit(-1);
}
if (pid > 0) // 父进程执行操作
{
execl("./write1", "./write1", "hello", "world", NULL);
}
if (pid == 0) // 子进程执行操作
{
char *arg1[] = {"./write2", "welcome", "zhaodeming", NULL};
execvp("/home/zdm/241/PROCESS_CODE/write2", arg1);
}
return 0;
}
对fork的改进对fork的改进更为彻底、简单粗暴
vfork是为子进程立即执行exec的程序而专设计的
无需为子进程复制虚拟内存页或页表,子进程直接共享父进程的资源,直到其成功执行exec或是调用exit退出
在子进程调用exec之前,将暂停执行父进程
子进程中无exec时,则先执行子进程,后执行父进程;
子进程有exec函数时,exec函数前的代码段先执行;执行到exec函数时候,父子进程调用顺序则又不确定(和fork一样)
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int count = 0;
pid_t pid = vfork();
if (pid < 0)
{
perror("pid error");
exit(-1);
}
if (pid > 0)
{
printf("%d\n",count);
}
if (pid == 0)
{
//count++; //尽量避免在子进程中修改全局变量,容易引发段错误;
//exit(-1);
execl("./write1","./write1",NULL);
}
return 0;
}
#include
#include
#include
int main(int aargc, char **argv)
{
system("./write1 system_using read");
sleep(3);
system("clear");
sleep(3);
system("ls -l");
sleep(3
);
system("clear");
sleep(3);
return 0;
}
更多用于异常退出;不会刷新stdio缓冲区;
内部封装了_exit;会刷新stdio缓冲区;atexit/on_exit注册了退出管理程序,则应使用exit
正常退出: main 调用return
异常退出: 1.任意地方调用exit/_exit; 2.被信号杀死; 3. 调用abort函数
以异常方式结束进程:abort ( )将引起进程异常的终止,此时所有已打开的文件流会自动关闭,所有的缓冲区数据也会自动写回。
回收进程资源
进程运行终止后,不管进程是正常终止还是异常终止的,必须回收进程所占用的资源。如何查看进程资源?
————————ps命令
为什么要回收进程的资源?
————————不回收资源会导致系统性能下降
父进程运行结束时,会负责回收子进程资源
./a.out进程的父进程是谁?
0,1,2三个进程:OS启动后抑制默默运行,直到关机OS结束运行;
pid=0的进程,称作调度进程;
pid=1的进程, 1.init进程,跟前端用户做交互;2.托管孤儿进程;3.原始父进程(位于/sbin/init目录下,可以restart*stop);
pid=2的进程;页精灵进程
僵尸进程:子进程终止后,父进程还在运行,那么在父进程没有回收子进程资源前,此时的子进程就是僵尸进程
孤儿进程:子进程还未结束,父进程先结束,子进程的资源无法回收,此时子进程就是孤儿进程
为了能够回收孤进程终止后的资源,孤儿进程会被托管给我们前面介绍的pid==1的init进程,每当被托管的子进程终止时,init会立即主动回收孤儿进程资源,回收资源的速度很快,所以孤儿进程没有变成僵尸进程的机会。
只能父进程等待子进程
函数原型 pid_t wait(int status)
#include
#include
#include
#include
#include
int main()
{
pid_t pid = fork();
if (pid == 0)
{
for (int i = 0; i < 3; ++i)
{
printf("children aaa\n");
sleep(1);
}
exit(3);
}
if (pid > 0)
{
printf("parents is ok\n");
// 1.获取子进程退出状态
int ret;
wait(&ret);
int num = WEXITSTATUS(ret);
printf("%d\n", ret);
// 2.wait(NULL); // 阻塞,直到子进程结束,再执行下面代码
}
return 0;
}
#include
#include
#include
#include
#include
//父进程等子进程,子进程等子子进程
void die(const char *msg)
{
perror(msg);
exit(1);
}
void child2_do()
{
printf("In child2: execute 'date'\n");
sleep(5);
if (execlp("date", "date", NULL) < 0)
{
perror("child2 execlp");
}
}
void child1_do(pid_t child2, char *argv)
{
pid_t pw;
do
{
if (*argv == '1')
{
pw = waitpid(child2, NULL, 0); // 一直等
}
else
{
pw = waitpid(child2, NULL, WNOHANG); // 立刻返回
}
if (pw == 0)
{
printf("In child1 process:\nThe child2 process has not exited\n");
sleep(1);
}
} while (pw == 0);
if (pw == child2)
{
printf("Get child2 %d.\n", pw);
sleep(5);
if (execlp("pwd", "pwd", NULL) < 0)
{
perror("child1 execlp");
}
}
else
{
printf("error occured!\n");
}
}
void father_do(pid_t child1, char *argv)
{
pid_t pw;
do
{
if (*argv == '1')
{
pw = waitpid(child1, NULL, 0); // 一直等待
}
else
{
pw = waitpid(child1, NULL, WNOHANG); // 立刻返回
}
if (pw == 0)
{
printf("In father process: \nThe child1 process has not exited.\n");
sleep(1);
}
} while (pw == 0);
if (pw == child1)
{
printf("Get child1 %d.\n", pw);
if (execlp("ls", "ls", "-l", NULL) < 0)
{
perror("father execlp");
}
}
else
{
printf("error occured ! \n");
}
}
int main(int argc, char **argv)
{
pid_t child1, child2;
if (argc < 3)
{
printf("Usage: waitpid [0 1] [0 1]\n");
exit(1);
}
child1 = fork();
if (child1 < 0)
{
die("child1 fork");
}
else if (child1 == 0)
{
child2 = fork();
if (child2 < 0)
{
die("child2 fork");
}
else if (child2 == 0)
{
child2_do();
}
else
{
child1_do(child2, argv[1]);
}
}
else
{
father_do(child1, argv[2]);
}
return 0;
}