【嵌入式Linux】线程与进程
1. 查看源代码
1. 线程与进程
在一些操作中,等待的时间是比较长的,为避免卡顿,或者反应不及时引入线程和进程。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
单进程单线程:一个人在一个桌子上吃菜。
单进程多线程:多个人在同一个桌子上一起吃菜。
多进程单线程:多个人每个人在自己的桌子上吃菜。
- ubuntu默认没有pthread库,需要安装。
sudo apt-get install -y glibc-doc manpages-posix-dev
- 查看pthrea手册,提示:
$ man pthreads
No manual entry for pthreads
#安装manpages
$ sudo apt-get install manpages-posix manpages-posix-dev
2. 编译各个程序并在Ubuntu运行;理解并解释运行状态和结果
2. 多线程
(1)线程的创建与终止
- 函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- 说明
参数 | 用法 |
---|---|
返回值 | 成功:0; 失败:错误号 //Linux环境下,所有线程特点,失败均直接返回错误号。 |
pthread_t | 当前Linux中可理解为:typedef unsigned long int pthread_t;//无符号长整形 |
参数1 | 传出参数,保存系统为我们分配好的线程ID |
参数2 | 通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。 |
参数3 | 函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。参数是函数指针,只能传递函数名,不能传递参数。所以就是只能有一个参数。 |
参数4 | 线程主函数执行期间所使用的参数。 |
- 编译并执行
gcc -o ptread pthread.c -lpthread
./ptread
由于线程之间的竞争可能导致每次输出不一样。
#include<unistd.h>
for (index = 0; index < array_num; index++) { /* 循环创建 5个线程 */
printf("In main: creating thread %ld.\n", index);
returned_code_err = pthread_create(&thr_num_array[index], NULL, thread_function, (void *) index); /* 创建线程 */
sleep(1); //创建线程后延时
(2)进程的连接与分离
- 函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 说明
参数 | 用法 |
---|---|
参数1 | 被等待的进程表示符 |
参数2 | 一个用户定义的指针,可以用来存储被等待线程的返回值 |
- 部分代码
for(index=0; index<NUM_THREADS; index++)
{
printf("Main: creating tid_array %ld\n", index);
err = pthread_create(&tid_array[index], NULL, thread_func, (void *)index); /* 创建线程 */
if (err)
{
printf("ERROR; return code from pthread_create() is %d\n", err);
exit(-1);
}
err = pthread_join(tid_array[index], &status); /*等待线程终止,并获取返回值*/
if (err)
{
printf("ERROR; return code from pthread_join() is %d\n", err);
exit(-1);
}
printf("Main: completed join with tid_array %ld having a status of %ld\n",index,(long)status);
}
- 编译并运行
gcc -o Untitled-2.out Untitled-2.c -lpthread -lm
./Untitled-2.out
(3)使用互斥量保护多线程同时输出
- 说明
函数 | 声明 | 作用 |
---|---|---|
pthread_mutex_init | int pthread_mutex_init(pthread_mutex_t mutex, const pthread_mutexattr_tmutexattr); | 初始化锁 |
pthread_mutex_lock | int pthread_mutex_lock(pthread_mutex_t *mutex); | 阻塞上锁 |
pthread_mutex_trylock | int pthread_mutex_trylock(pthread_mutex_t *mutex); | 非阻塞上锁 |
pthread_mutex_unlock | int pthread_mutex_unlock(pthread_mutex_t *mutex); | 解锁 |
pthread_mutex_destroy | int pthread_mutex_destroy(pthread_mutex_t *mutex); | 销毁锁,释放资源 |
- 编译并运行
gcc -o Untitled-4.out Untitled-4.c -lpthread -lm
./Untitled-4.out
(4)条件变量使用
条件变量用来阻塞一个线程,直到其他的线程通知它条件已经满足为止。
- 说明
函数 | 声明 | 作用 |
---|---|---|
pthread_cond_init | int pthread_cond_init(pthread_cond_t cond, pthread_condattr_tcond_attr); | 初始化条件变量 |
pthread_cond_wait | int pthread_cond_wait(pthread_cond_t cond, pthread_mutex_tmutex); | 阻塞等待 |
pthread_cond_timedwait | int pthread_cond_timedwait(pthread_cond_t cond, pthread_mutex_tmutex, const struct timespec *abstime); | 超时等待 |
pthread_cond_signal | int pthread_cond_signal(pthread_cond_t *cond); | 唤醒一个等待该条件的线程 |
pthread_cond_destroy | int pthread_cond_destroy(pthread_cond_t *cond); | 销毁条件变量 |
- 编译并执行
gcc -o Untitled-5.out Untitled-5.c -lpthread -lm
./Untitled-5.out
3. 多进程
(1)获取测试环境变量代码
- 通过main()函数的第三个参数env循环获取
#include <stdio.h>
int main(int argc, char * argv[], char *env[])
{
int i = 0;
while (env[i])
puts(env[i++]);
return 0;
}
- 通过environ全局变量获取
#include <stdio.h>
extern char ** environ;
int main(int argc, char * argv[])
{
int i = 0;
while (environ[i]) puts(environ[i++]);
return 0;
}
- 通过getenv函数获取
#include <stdio.h>
#include <stdlib.h>
int main ()
{
printf("PATH : %s\n", getenv("PATH"));
printf("HOME : %s\n", getenv("HOME"));
printf("ROOT : %s\n", getenv("ROOT"));
printf("SHELL : %s\n", getenv("SHELL"));
printf("USERNAME : %s\n", getenv("USERNAME"));
printf("LANG : %s\n", getenv("LANG"));
printf("XDG_SESSION_DESKTOP : %s\n", getenv("XDG_SESSION_DESKTOP"));
printf("OLDPWD : %s\n", getenv("OLDPWD");
return(0);
}
(2)创建进程程序代码
- fork()函数
fork()函数从运行着的进程中分裂出一个子进程,它通过拷贝父进程的方式创建子进程。子进程与父进程有相同的代码空间、文件描述符等资源。
- 用法
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
-
进程创建后,子进程与父进程开始并发执行,执行顺序由内核调度算法来决定。fork()函数如果成功创建了进程,就会对父子进程各返回一次,其中对父进程返回子进程的PID,对子进程返回0;失败则返回小于0的错误码。
-
编译执行
gcc -o fork.out fork.c
./fork.out
(3)子进程加载新程序代码
- 原理
在创建进程后子进程与父进程有相同的代码空间;实际应用中,需要子进程 去执行另外一个程序,可以在子进程中调用exec族函数它的代码段完全替换为 新的程序,并从这个新进程的main函数开始执行。
- exec函数族
函数 | 后缀 | 说明 |
---|---|---|
execl | l | 表示使用list形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec中给出,最后一个参数需要是NULL用以表示没有更多的参数了,如execl()函数。 |
execle | e | 表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以NULL结束,如execvpeO函数。而无此后缀的函数则将当前进程 的environ变量复制给新的程序,如execv()函数。 |
execlp | p | 表示使用filename做参数,如果filename中包含则视其为路径名,否则将会在PATH环境变量所指定的各个目录中搜索该文件,如exedp()函数。无后缀p则必须使用路径名来指定可执行文件的位置, 如execl()函数。 |
execv | v | 表示使用vector形式来传递新程序的参数,传给新程序的所有 参数放入一个字符串数组中,数组以NULL结束以表示结尾,如execv()函数。exec族函数只有在出错的时候才会返回,如果成功,该函数无返回,否则返 回-1。 |
- 运行结果
(4)使用daemon创建守护进程
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,它不需要用户输入就能运行并提供某种服务。
守护进程的父进程是init进程,因为它真正的父进程在fork出该子进程后就先于核子进 程exit退出了,所以它是-个由init领养的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出(无论是向标准输出设备还是标准错误输出设备的输出)都需要特殊处理。
- daemon()函数
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数 | 说明 |
---|---|
nochdir | 如果传入0,则daemon函数将调用进程的工作目录设置 为根目录,否则保持原有的工作目录不变 |
noclose | 如果传入0,则daemon函数会将标准输入、标准输出、 标准错误重定向到/dev/null文件中,否则不改变这些文件描述符。该函数如果成功则返回0,否则返回-1,并设置errno |
- 编译并执行
gcc -o daemon.out daemon.c
./daemon.out
cat /tmp/daemon.log
(5)信号函数sigaction的使用
信号(signal),又称为软中断信号,用来通知进程发生了异步事件。进程之 间可以互相发送信号,内核也可以因为内部事件而给进程发送信号。注意,信号 的作用仅仅是通知进程发生了什么事件,并不向该进程传递任何数据。
- 信号处理方式
序号 | 处理方法 |
---|---|
1 | 使用类似中断的处理程序,对于需要处理的信号,进程可以指 定相应的处理函数,由该函数来负责对信号的处理 |
2 | 忽略某个信号,对该信号不做任何处理,就像未发生过一样 |
3 | 对该信号的处理保留系统默认值,这种缺省操作对于大部分的 信号来说就是使得进程被终止 |
- sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数 | 说明 |
---|---|
返回值 | 函数成功返回0,否则返回-1 |
signum | 指出需要改变处理方法的信号,如SIGINT信号,但SIGKILL 和SIGSTOP这两个信号是不可捕捉的 |
act/oldact | sigaction结构体的指针,act是要设置的对信号 的新处理方式,而oldact则为原来对信号的处理方式 |
- 编译并执行
gcc -o sigaction.out sigaction.c
./sigaction.out
3. 运行开发板
准备好虚拟机和ubuntu系统,本实验版本为ubuntu18.04x64
参考文档:https://www.csdn.net/tags/OtTaYgwsODIwOTAtYmxvZwO0O0OO0O0O.html
下载、安装、运行QEMU
下载
- 下载成功后,进入ubuntu-18.04_imx6ul_qemu_system目录,执行install_sdl.sh,如下所示:
- 在Ubuntu 18.04系统中,执行如下命令,可以得到一个目录
ubuntu-18.04_imx6ul_qemu_system
:
$ git clone https://e.coding.net/weidongshan/ubuntu-18.04_imx6ul_qemu_system.git
- 安装SDL
- 下载成功后,进入
ubuntu-18.04_imx6ul_qemu_system
目录,执行install_sdl.sh
如下所示:
$ cd /home/suliu/Desktop/ubuntu-18.04_imx6ul_qemu_system/
$ ls
imx6ull-system-image qemu-imx6ull-gui_test.sh source
install_sdl.sh qemu-imx6ull-nogui.sh ubuntu-18.04_sdl-package
qemu README.md
qemu-imx6ull-gui.sh rootfs_test
$ install_sdl.sh
运行QEMU
- 必须在Ubunut的桌面环境下启动终端,执行
./qemu-imx6ull-gui.sh
如下所示:
$ cd /home/suliu/Desktop/ubuntu-18.04_imx6ul_qemu_system/
$ ls
imx6ull-system-image qemu-imx6ull-gui_test.sh source
install_sdl.sh qemu-imx6ull-nogui.sh ubuntu-18.04_sdl-package
qemu README.md
qemu-imx6ull-gui.sh rootfs_test
$ ./qemu-imx6ull-gui.sh
- 报错(BUG)
x6ull-gui.sh
qemu-system-arm: warning: nic imx.enet.0 has no peer
qemu-system-arm: warning: nic imx.enet.1 has no peer
libGL error: MESA-LOADER: failed to open vmwgfx (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: vmwgfx
libGL error: MESA-LOADER: failed to open vmwgfx (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: vmwgfx
libGL error: MESA-LOADER: failed to open swrast (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: swrast
X Error of failed request: GLXBadContext
Major opcode of failed request: 153 (GLX)
Minor opcode of failed request: 6 (X_GLXIsDirect)
Serial number of failed request: 114
Current serial number in output stream: 113
- 解决(SOLVE):
这一步非常关键,因为韦东山老师的文档里没有写,需要执行以下命令安装驱动
$ sudo apt --fix-broken install
- 重新执行
./qemu-imx6ull-gui.sh
下载、编译内核
参考:http://wiki.100ask.org/Qemu
安装 lzop 工具,执行如下指令
// 先强制安装一遍依赖
$ sudo apt-get install lzop // 再安装软件
下载源码
$ git clone https://e.coding.net/codebug8/repo.git
$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu
$ ../repo/repo init -u https://e.coding.net/weidongshan/manifests.git -b linux-sdk -m imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify
- 下载成功后,可以得到如下内容
$ cd 100ask_imx6ull-qemu/
$ ls
buildroot2019.02 linux-4.9.88 qemu ToolChain
设置工具链
- 执行如下命令:
$ export ARCH=arm
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
配置、编译内核
- 安装交叉编译器(这一步很重要,否则无法编译)
$ sudo apt install gcc-arm-linux-gnueabihf // 安装编译器。
- 执行如下命令:
$ cd linux-4.9.88
$ make mrproper
$ make 100ask_imx6ull_qemu_defconfig
$ make zImage //这一步需要等待一段时间(10mins)
在QEMU中使用新的zImage
- 把编译出来的zImage复制到QEMU目录
ubuntu-18.04_imx6ul_qemu_system/imx6ull-system-image
即可:
$ cd ~/ubuntu-18.04_imx6ul_qemu_system/
替换LCD驱动程序
QEMU所用的内核里已经带有LCD驱动程序了,要测试我们编写的驱动程序,需要做2件事
-
把
lcd_drv.c
放到内核目录linux-4.9.88/drivers/video/fbdev
-
修改
linux-4.9.88/drivers/video/fbdev/Makefile
如下:
#obj-y += 100ask_qemu_fb.o
obj-y += lcd_drv.o
- 最后,即可重新执行
make zImage
编译内核,内核里就含有新的驱动程序了。