参考书本《高质量嵌入式linux C编程》
一、为何需要多线程,为何需要并发
首先解释什么是线程:通常在硬盘上的一个可执行文件(例如在windows上的是.exe文件,在Linux上是只要有可执行权限x) 称之为程序。当运行一个程序之后,程序就会加载到内存中,在运行的过程中就称之为线程。因为可以发现,一个程序可以创造多个线程。但为何需要多线程呢?为何需要并发呢?作为一枚单片机爱好者,在写裸机程序的时候,只有一个main函数,此时同一时间内只能执行一个程序。就比如说,一个快餐店只有一个服务员,他既要负责做餐,还要负责卖,还要负责收钱,他是不是很累。如果有了鸣人(火影忍者人物)的分身术,他可创造三个分身,一个专门做餐,一个专门收钱,最后一个专门负责卖餐。这就是多线程的应用。
1.1 进程
对于单cpu的计算机来说,每个时刻只能执行一条指令代码。而linux是多任务系统,那么linux是如何实现多进程同时执行呢?其实在linux中使用了进程调度算法,首先,为每个进程分配一定的运行时间,这个时间往往很短,然后根据算法,从众多进程中挑选出一个进程进行运行,其他进程都处于停滞状态,当运行时间耗尽之后或者进程执行完毕之后,Linux会重新进行算法调度,挑选下一个进程进行运行。其中,每个进程可以运行时间很短,一般都是毫秒级。因此,从使用者来说,感觉就像多个线程同时运行。需要注意的是,Linux系统中会为每个进程很配进程控制块(PCB),PCB中包含了很多重要的信息供系统和进程本身使用,最主要的有进程ID,也称之为进程标识符,是一个非负整数,它与每一个进程一一对应。
1.2 进程分类
进程一般分为交互进程,批处理进程,和守护进程
1.3父子进程
父进程和子进程的关系是管理与被管理,当父进程结束,子进程也随之结束。而子进程结束,父进程任然可以存在。子进程如果结束了,而父进程却没有为其收尸,子进程就会变成僵尸进程,其不占用任何内存空间,仅仅会在进程列表中占有一个位置。太多僵尸进程会造成系统奔溃。即使当父进程在结束之前没有为子进程收尸,系统init进程会自动接收子进程为其收尸,但是父进程一直处于死循环没有结束,子进程的产生的僵尸进程就会一直存在。
如何避免僵尸进程:父进程通过wait和waitpid等函数等待子进程结束,但这会导致父进程挂起。如果父进程很忙,那么可以调用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程就会收到信号,可以在hander中调用wait回收。如果父进程不关心子进程什么时候结束,可以使用singal(SIGCHLD,SIG_IGN),忽略,然后子进程结束后,由系统内核回收。
头文件
#include
#include
定义
pid_t wait(int *status)
函数说明
父进程获取子进程的状态
返回值
返回子进程ID,错误-1
二、 Linux进程管理
2.1 各种进程命令
查看进程列表,静态
ps -aux或者ps -lax
查询进程
pgrep 参数 程序名
-l 列出 程序名和进程ID
-o 进程起始ID
-n 进程终止ID
终止进程
kill 【信号代码】 进程ID
一般来说信号代码 -9,表示强行终止
killall 通过程序名直接杀死进程
动态监视进程
top
三、进程创建
3.1 获取进程
头文件
#include
#include
定义
pid_t getpid(void)
函数说明
获取正在执行进程的ID
返回值
返回进程ID
头文件
#include
#include
定义
pid_t getppid(void)
函数说明
获取父进程的ID
返回值
返回父进程ID
3.2 创建进程
头文件
#include
#include
定义
pid_t fork(void)
函数说明
创建新进程,子进程只是复制父进程的资源,子进程有其自己的task_struct和PID,而且子进程和父进程所停留的代码位置是一样的。父子进程之间不会共享数据空间,堆栈。在子进程中修改变量的数据不会影响父进程。
返回值
在父进程中返回子进程ID,在子进程返回0,错误-1
头文件
#include
#include
定义
pid_t vfork(void)
函数说明
创建新进程,子进程共享父进程数据空间,堆栈。在子进程中修改变量的数据会影响父进程。
返回值
在父进程中返回子进程ID,在子进程返回0,错误-1
代码测试:
*/***********************************************************************
> File Name: my_fork.c
> Author:silence
> Mail: 1774623251@qq.com
> Created Time: 2018年12月19日 星期三 12时38分45秒
************************************************************************/
#include
#include
#include
#include
#include
//目的:觀察子進場是否共享父進場資源
int main(void)
{
pid_t pid;
int tmp = 10;
pid = fork();
if(pid < 0)
{
printf("create progress failure
");
exit(1);
}
if(pid == 0)
{
//child progress
printf("-----I am child progress-----
");
tmp = 20;
printf("tmp = %d
",tmp);
}
else
{
//father progress
sleep(1);
printf("-----I am father progress-----
");
tmp = 30;
printf("tmp = %d
",tmp);
}
return 0;
}
运行结果:
-----I am child progress-----
tmp = 20
-----I am father progress-----
tmp = 10
结论:fork创建的子进程不共享父进程资源
/*************************************************************************
> File Name: my_vfork.c
> Author:silence
> Mail: 1774623251@qq.com
> Created Time: 2018年12月19日 星期三 12时50分45秒
************************************************************************/
#include
#include
#include
#include
#include
//目的:vfork()創察子進場是否共享父進場資源
int main(void)
{
pid_t pid;
int tmp = 10;
pid = vfork();
if(pid < 0)
{
printf("create progress failure
");
exit(1);
}
if(pid == 0)
{
//child progress
printf("-----I am child progress-----
");
tmp = 20;
printf("tmp = %d
",tmp);
exit(0);
}
else
{
//father progress
sleep(1);
printf("-----I am father progress-----
");
printf("tmp = %d
",tmp);
}
return 0;
}
运行结果:
-----I am child progress-----
tmp = 20
-----I am father progress-----
tmp = 20
my_vfork: cxa_atexit.c:100: __new_exitfn: Assertion `l != NULL' failed.
Aborted (core dumped)
结论:vfork创建的子进程共享父进程资源
运行程序出现问题:my_vfork: cxa_atexit.c:100: __new_exitfn: Assertion `l != NULL' failed.
Aborted (core dumped)
vfork():同上返回两个值,区别在于vfork()子进程共享父进程的地址空间,即子进程运行在父进程的地址空间上,子进程对数据的修改父进程同样能看到。特别注意,使用vfork() 时子进程中需调用exec 或exit 父进程才可能被调度运行,如果在调用exec 或 exit之前子进程要依赖父进程的的某个行为,则会造成死锁。
a.out: cxa_atexit.c:100: __new_exitfn: Assertion `l != ((void *)0)' failed.
Aborted (core dumped)
这种错误的一个原因是由于子进程中没有调用exit 或 exec
在子进程中加入
exit(0);就可以运行正确。
3.3 exec族函数
作用:用来替换调用进程执行的程序。
在上面的例子中,在创建的子程序中任然和父进程有相同的代码空间,而实际中子程序都需要执行另外一个程序,
这种情况下,就可以使用exec族函数完全代替子进程的程序作为新程序。
#include
exterm vooid **environ;
int execl(const char *path,cosnt char *arg,...);
int execlp(const char *file,cosnt char *arg,...);
int execle(const char *path,cosnt char *arg,...,char *const envp[]);
int execv(const char *path,char *cosnt argv[]);
int execvp(const char *file,char *cosnt argv[]);
int execvpe(const char *file,char *cosnt argv[],char *const envp[]);
后缀P:表示使用文件file做参数,如果file中包含"/"视作路径,否则在PATH环境变量所指的各个目录搜索文件。
后缀L:表示使用list形式传递参数,传入新程序的所有参数都是可变的,但是最后是以NULL结尾。
后缀V:传入的参数放入数组中
/*************************************************************************
> File Name: my_execl.c
> Author:
> Mail:
> Created Time: 2018年12月19日 星期三 14时43分17秒
************************************************************************/
//在本文件中创建子进程,然后调用new_progress 可执行文件
#include
#include
#include
#include
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("Create progress failure
");
exit(1);
}
if(pid == 0)
{
//child progress
printf("----I am child----
");
execl("./new_progress","new_progress",NULL);
exit(2);
}
else
{
//father progress
printf("----I am father progress----
");
}
return 0;
}
/**********************************************************************
> File Name: new_progress.c
> Author:
> Mail:
> Created Time: 2018年12月19日 星期三 14时56分00秒
*********************************************************************/
#include
int main(void)
{
printf("---- new_progress is runing ----
");
return 0;
}
运行结果:
----I am father progress----
----I am child----
---- new_progress is runing ----