《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
进程是系统资源的最小单元,很重要。
7.1 linux进程的基本概念
- 定义:一个程序的一次执行过程,同时也是资源分配的最小单元。程序是静态的,而进程是动态的。
- 进程控制块:linux系统用进程控制块描述进程,task_struct,在 include/linux/sched.h
- PID,进程唯一标识;PPID,父进程的PID
#include <unistd.h> /* Get the process ID of the calling process. */
extern __pid_t getpid (void) __THROW; /* Get the process ID of the calling process's parent. */
extern __pid_t getppid (void) __THROW;
进程相关的还有用户和用户组标识、进程时间、资源利用的函数,参考APUE.
- 进程运行的状态

- 进程的结构:主要包含数据段、代码段、堆栈段
- 进程模型:用户态和内核态
- linux启动进程的两种方式:
- 手动启动:前台启动,最常见的是在终端里输入命令,该命令的执行就是一个进程; 后台启动,用&,不影响终端,在终端后面默默运行。
- 调度启动:制定时间运行,有一些命令,at命令可以在指定时刻执行相关进程;cron命令可以自动周期性的执行相关进程。
常用进程相关命令:

7.2 linux进程编程
7.2.1 fork
- 从已创建的进程中创建一个新的进程,新进程叫子进程,原进程叫父进程。
- 子进程是父进程的复制,集成父进程的绝大部分内容,包括:整个进程的地址空间、进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等;
- 子进程独有的部分:进程号、资源使用、计时器等
- fork的大体流程:父进程执行fork——>父进程复制出一个子进程——>父子进程从fork函数返回开始分别在两个地址空间同时运行,通过返回值区分父子进程。
- fork的开销比较大,复制这么多东西,想想都觉得累。有些unix系统创建了vfork()函数,vfork创建新进程时,不产生父进程的副本,允许父子进程访问相同的物理内存而伪装成拷贝父进程。但是子进程需要改变内存时(写),才复制父进程,这就是“写时复制”,linux的fork就是调用vfork函数实现的。
#include <sys/types> // pid_t
#include <unistd.h> pid_t fork( void );
参数:
返回值:
0:子进程
>0:子进程pid,父进程
-1:出错
注意事项:
fork调用一次,就创建一个子进程,所以if、else if等分支处理时,不能多次调用,应该调用一次,记下返回值,然后if else等使用此返回值。
/* 7-1,fork */ #include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <fcntl.h> // open,fcntl
#include <sys/types.h> int main(int args, char *argv[])
{
pid_t pid_rtn; pid_rtn = fork();
if( pid_rtn == )
{
printf("\r\nChild thread, pid %d, ppid %d",getpid(),getppid());
}
else if( pid_rtn > )
{
sleep(); // 如果父进程先结束,则子进程会被init进程收养,用getppid时获取的就不是创建他的父进程了
printf("\r\nParent thread, pid %d, child %d",getpid(),pid_rtn);
}
else
{
printf("\r\nfork err.");
} printf("\r\nfinish.\r\n");
exit();
} $ ./example
Child thread, pid 4087, ppid 4086
finish. Parent thread, pid 4086, child 4087
finish. 如果父进程不睡1s,则运行结果如下:
$ ./example Parent thread, pid 4121, child 4122
finish.
$
Child thread, pid 4122, ppid
finish. $ ps -A
*
2326 ? 00:00:01 upstart // upstart就是ubuntu的init进程,对于父进程已经结束的子进程,会被这个进程“收养”
*
7.2.2 exec函数族
- 执行另一个程序,除了pid外,其他全被新的进程替换
- 一般先fork,然后exec执行想执行的程序
- exec注意事项:一定要加上错误判断语句,exec很容易出错,常见错误有:
- 找不到文件或路径,errno=ENOENT
- argv和envp忘记用NULL结束,errno=EFAULT;
- 没有对应可执行文件的运行权限,errno=EACCES
- 6个函数中,真正的系统调用只有execve,其他都是库函数,通过调用execve实现
#include <unistd.h> int execl(const char *path, const char *arg, ...) // list
int execv(const char *path, char *const argv[]) // vector
int execle(const char *path, const char *arg, ..., char *const envp[]) // enviroment
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])
参数:
path和file:查找方式,path完整的文件目录路径;file(p结尾的函数)只给出文件名,系统按照环境变量PATH指定的路径查找;
arg...和argv[]:参数传递方式,list和vector,这些参数必须以NULL结尾,以可执行程序命令本身开头;
envp:环境变量,e结尾,指定要执行的进程所使用的环境变量
返回值:-1 出错

/* 7-2,exec */
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <fcntl.h> // open,fcntl
#include <sys/types.h>
int main(int args, char *argv[])
{
pid_t pid_rtn;
pid_rtn = fork();
if( pid_rtn == 0 )
{
execlp("ps","ps","-A","NULL"); // 第一个ps是文件名,后面是参数,输入参数时,第一个参数是要运行的程序,跟在shell里输入是一样的,注意要用NULL结尾
}
printf("\r\nfinish.\r\n");
exit(0);
}
相当于执行了“ps -A”命令,运行结果:
PID TTY STAT TIME COMMAND
1 ? Ss 0:12 /sbin/init splash
2 ? S 0:00 [kthreadd]
......
7.2.3 exit和_exit
- 两个函数会停止所有操作,清除PCB等数据结构;
- 两个函数有差别:
- _exit:直接停止运行, 清除进程使用的内存空间,清除内核中的数据结构;
- exit = “清理IO缓存”+_exit, 清理IO缓存,指检查文件的打开情况,把文件缓冲区中的内容写回文件。linux里有“缓冲IO”操作,例如printf、fgets等,使用缓冲区,类似cache。
- 只使用exit()就可以了
进程调用exit()和_exit()后不会立即退出,而是进入僵死zombie状态,变成僵尸进程,僵尸进程只在进程列表里保留一个位置,记录该进程的退出状态等供其他进程收集(一般是父进程用wait收集)。
#include <unistd.h> // _exit
#include <stdlib.h> // exit void exit( int status );
void _exit( int status); 参数:
status 可以返回本进程(调用exit的进程)的退出状态,一般0表示正常,其他数值表示出错,进程非正常结束;
父进程用wait()系统调用接收子进程的返回值。
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
int main(int args, char *argv[])
{
printf("Start.\n");
printf("content in buffer.");
_exit(0);
}
$ ./example // 在缓冲区里就没有了,因为_exit不刷缓冲区
Start.
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
int main(int args, char *argv[])
{
printf("Start.\n");
printf("content in buffer.");
exit(0);
}
$ ./example // 在缓冲区里的也刷出来了,exit干的
Start.
content in buffer.
【注意】
printf遇到“\n”换行符时自动从缓冲区中将记录读出
7.2.4 wait和waitpid
- wait阻塞等待1个子进程结束,如果该进程在阻塞时接到了一个指定的信号,则阻塞也可能终止。如果没有子进程或者子进程已经结束,则wait会立即返回。
- waitpid比wait功能丰富,可提供非阻塞、作业控制、指定待等待进程等功能
#include <sys/types.h>
#include <sys/wait.h> pid_t wait( int * status );
参数:
status:返回子进程的退出状态和异常终止状态,若为NULL,则不获取。可以通过一些linux特定的宏来测试具体状态信息。
【重要】:进程退出有正常退出(子进程exit或者return),此时的状态记为“正常退出状态”;还有异常退出的情况,例如被信号中断等,这时的状态记为“异常终止状态”。status可以反映这两种状态。
union wait
{
int w_status;
struct
{
# if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int __w_termsig:7; /* Terminating signal. */
unsigned int __w_coredump:1; /* Set if dumped core. */
unsigned int __w_retcode:8; /* Return code if exited normally. */
unsigned int:16;
# endif
} __wait_terminated; // 正常退出和异常终止,格式
struct
{
# if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int __w_stopval:8; /* W_STOPPED if stopped. */
unsigned int __w_stopsig:8; /* Stopping signal. */
unsigned int:16;
# endif
} __wait_stopped; // 暂停,stop,格式
};
常用宏:
WIFEXITED(status),若为正常退出,则返回真。若为真,可用WEXITSTATUS(status)获取exit返回的状态;
WIFSIGNALED(status),若子程序为异常终止,则返回真(被信号终止),可用WTERMSIG(status)获取子进程终止的信号编号;可用WCOREDUMP(status)检查是否产生core文件,产生时为真;
WIFSTOPPED(status),如果子程序暂停,则为真,可通过WSTOPSIG(status),获取使子程序暂停的信号编号
WIFCONTINUED(status),若暂停后又继续的子进程返回状态,则为真,仅用于waitpid。
#define WIFEXITED(status) (((status) & 0x7f)== 0)
#define WIFSIGNALED(status) (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
#define WEXITSTATUS(status)(((status) & 0xff00) >> 8)
#define WTERMSIG(status) ((status) & 0x7f)
#define WSTOPSIG(status) (((status) & 0xff00) >> 8)
返回值:
成功:已结束运行(被等待的)的子进程的进程号
失败:-1 pid_t waitpid( pid_t pid, int *status, int options );
参数:
pid: >0,等待进程ID=pid的子进程,不管别的;
=-1,等待任何一个子进程,与wait()作用一样;
= 0,等待“组ID==调用进程组ID”的任一子进程;
<-1,等待“组ID==pid绝对值”的任一子进程
status:同wait()函数
options:sya
WNOHANG:不阻塞
WUNTRACED:若实现某支持作业控制,则由 pid 指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态
0:同wait(),阻塞
返回值:
正常:已结束运行的子进程的进程号
使用WNOHANG且没有子进程:0
调用出错:-1 【注意】
1. 关于几个测试退出状态的特殊的宏
/* 7-4,waitpid */
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <sys/wait.h>
int main(int args, char *argv[])
{
pid_t pid_fork,pid_wait;
int status;
pid_fork=fork();
if( pid_fork == 0 ) // child
{
sleep(5);
exit(0);
}
else if( pid_fork > 0 )
{
do
{
pid_wait = waitpid(pid_fork,&status,WNOHANG);
if( pid_wait != pid_fork )
printf("child thread %d is not over\r\n",pid_fork);
else
printf("child thread %d is over,status 0x%x\r\n",pid_fork,status);
sleep(1);
}
while(pid_wait!=pid_fork);
}
else
{
printf("fork err code %d.\r\n",pid_fork);
}
exit(0);
}
$ ./example
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is over,status 0x0
$ ./example // 子进程exit(-1)时,waitpid的获取的值是0xff00,高位是exit的返回值,需要用到宏了
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is over,status 0xff00
7.3 守护进程
7.3.1 守护进程概念
守护进程,也叫deamon进程,是后台服务进程;系统引导载入时启动,系统关闭时终止,独立于控制终端;常用于周期性的执行某种任务或等待处理某些事件。守护进程已d结尾,例如crond、lpd等。
控制终端:系统与用户进行交流的界面称为终端,每个从此终端开始运行的进程都会依赖这个终端,这个终端就是这些进程的控制终端。控制终端关闭时,相应的进程都会关闭。但是守护进程不受影响。
守护进程不受终端、用户和其他变化的影响,直到系统关闭时才退出。
7.3.2 编写守护进程
步骤:
7.3.2.1 创建子进程,父进程退出
父进程退出后,子进程编程了孤儿进程,被init进程收养。 形式上做到了与控制终端的脱离。
!!!7.3.2.2 在子进程中创建新会话
先了解基本概念:进程组、会话组、会话期
进程组:一个或多个进程的集合,每个进程组都有一个组长进程,进程组ID=组长PID
会话组:一个或多个进程组的集合
会话期:通常一个会话开始于用户登录,终止与用户退出,在此期间该用户运行的所有进程都属于这个会话期。

setsid():创建新的会话,并担任该会话组的组长。调用后起到3个作用:
- 让进程摆脱原会话的控制;
- 让进程摆脱原进程组的控制
- 让进程摆脱原控制终端的控制
总之,跟之前的控制终端、进程组、会话组都没有关系了。使进程完全独立出来,从而摆脱所有其他进程的控制 #include <sys/types.h>
#include <unistd.h> pid_t setsid( void );
返回值:
成功:该进程组ID
出错:-1
7.3.2.3 改变当前目录为根目录
通常的做法是将守护进程的当前目录设置为根目录,用chdir()系统调用。
7.3.2.4 重设文件权限掩码
umask(0),基本思路是给最大权限。
7.3.2.5 关闭文件描述符
父进程那继承来的文件描述符,一般不用,浪费,关闭。 连基本的输入输出都没用了,setsid时已经失去联系了,可以关了。
/* 7-5,deamon */ #include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h> int main(int args, char *argv[])
{
pid_t pid_fork;
int i;
int fd;
char buf[]="The deamon info.\n"; pid_fork = fork();
if( pid_fork < )
{
printf("fork err.\r\n");
}
else if( pid_fork > )
{
exit();
} // only child enter
setsid();
chdir("/");
umask();
for( i=;i<getdtablesize();i++ ) // 终端也关了,printf没有效果了,需要用别的调试方法
close(i); if( fd=open("/tmp/log", O_RDWR|O_CREAT,) < )
printf("open file err\r\n");
while()
{
write(fd,buf,sizeof(buf));
sleep();
} exit();
}
7.3.3 守护进程的出错处理
printf不好使,咋办?用linux提供的syslog服务,系统中有syslogd守护进程。不通版本linux的syslog日志文件的位置可能不通。
#include <syslog.h> void openlog( char * ident, int options, int facility );
参数:
ident:向每个消息加入的字符串,通常为程序的名称;
option:LOG_CONS,如果消息无法送到系统日志服务,则直接输出到系统控制终端
LOG_NDELAY:立即打开系统日志服务的连接。在正常情况下,直接发送到第一条消息时才打开连接
LOG_PERROR:将消息也同时送到 stderr 上
LOG_PID:在每条消息中包含进程的 PID facility: 指定程序发送的消息类型
LOG_AUTHPRIV:安全/授权信息
LOG_CRON:时间守护进程(cron 及 at)
LOG_DAEMON:其他系统守护进程
LOG_KERN:内核信息
LOG_LOCAL[0~7]:保留
LOG_LPR:行打印机子系统
LOG_MAIL:邮件子系统
LOG_NEWS:新闻子系统
LOG_SYSLOG:syslogd 内部所产生的信息函数传入值
LOG_USER:一般使用者等级信息
LOG_UUCP:UUCP 子系统 void syslog(int priority, char *format, ...)
参数: priority ,指定消息的重要性,
LOG_EMERG:系统无法使用
LOG_ALERT:需要立即采取措施
LOG_CRIT:有重要情况发生
LOG_ERR:有错误发生
LOG_WARNING:有警告发生
LOG_NOTICE:正常情况,但也是重要情况
LOG_INFO:信息消息
LOG_DEBUG:调试信息
format,同printf void closelog( void )
《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发的更多相关文章
- 《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程
前段时间看APUE,确实比较详细,不过过于详细了,当成工具书倒是比较合适,还是读一读这种培训机构的书籍,进度会比较快,遇到问题时再回去翻翻APUE,这样的效率可能更高一些. <嵌入式linux应 ...
- 嵌入式Linux应用程序开发详解------(创建守护进程)
嵌入式Linux应用程序开发详解 华清远见 本文只是阅读文摘. 创建一个守护进程的步骤: 1.创建一个子进程,然后退出父进程: 2.在子进程中使用创建新会话---setsid(): 3.改变当前工作目 ...
- 嵌入式linux应用程序调试方法
嵌入式linux应用程序调试方法 四 内存工具 五 C/C++代码覆盖.性能profiling工具 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形. 许多小组花了许许多多时间来 ...
- gdbserver远程调试嵌入式linux应用程序方法
此处所讲的是基于gdb和gdbsever的远程调试方法.环境为:PC机:win7.虚拟机:10.04.下位机:飞嵌TE2440开发板. 嵌入式linux应用程序的开发一般都是在linux里面编写好代码 ...
- 嵌入式linux应用程序移植方法总结
嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...
- linux进程控制开发实例
fork.c #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include < ...
- 开发新手教程【三】Arduino开发工具
Arduino开发环境搭建 获取Arduino IDE开发工具 下载地址 :http://arduino.cc/en/Main/Software 能够下载release 版.Beta版和前期版本号 A ...
- asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发4- 后台模板html页面创建
上一篇教程<asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发3-登录模块开发>完成了本项目的登录模块,登录后就需要进入后台管理首页了,需要准备一个后台模 ...
- asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发2-Model层建立
上篇(asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发1-准备工作)文章讲解了开发过程中的准备工作,主要创建了项目数据库及项目,本文主要讲解项目M层的实现,M层这里 ...
随机推荐
- 统计最长回文串(传说中的Manacher算法)Hihocoder 1032
算法的核心就在这一句上了:p[i] = min(p[2*id-i], p[id] + id - i); #include <iostream> #include <cstdio> ...
- AtCoder Beginner Contest 053 ABCD题
A - ABC/ARC Time limit : 2sec / Memory limit : 256MB Score : 100 points Problem Statement Smeke has ...
- 模拟IO 读写压力测试
#### 本实验室通过创建一个测试表myTestTable ,分配在一个足够大小的表空间. ###然后通过 insert select 方式,创建100个后台进程进行读写操作,每个后台进程预计时间20 ...
- Win10系统特别卡的一个原因
我遇到的Win10特别卡的原因是它自带的一个杀毒软件 迈克菲(McAfee)导致的,在卸载之前电脑真的特别卡,打开一个窗口都卡,,卸载了之后瞬间感觉电脑飞起来了.... 当然还有很多原因会导致电脑卡, ...
- thinkPHP--模块分组
启用分组模块非常简单,配置下APP_GROUP_LIST参数和DEFAULT_GROUP参数即可. 'APP_GROUP_LIST'=>'Admin,Home', 'DEFAULT_GROUP' ...
- java数据结构和算法06(红黑树)
这一篇我们来看看红黑树,首先说一下我啃红黑树的一点想法,刚开始的时候比较蒙,what?这到底是什么鬼啊?还有这种操作?有好久的时间我都缓不过来,直到我玩了两把王者之后回头一看,好像有点儿意思,所以有的 ...
- HTML中实现Table表头点击升序/降序排序
题目:如下图,请实现表格信息的排序功能,当点击表头的属性区域,将表格信息进行排序切换功能,即第一次点击为降序排序,再一次点击进行升序排序. 姓名 力量 敏捷 智力 德鲁伊王 17 24 13 月之骑士 ...
- ubuntu中使用eclipse开发android,logcat显示问题
〜/工作区/ .metadata / .plugins / org.eclipse.core.runtime / .settings / com.android.ide.eclipse.ddms.pr ...
- uvm_reg——寄存器模型(三)
uvm_reg 是uvm_reg_field , 包含所有uvm_reg_field 所有的函数.
- Android学习总结(十一)———— Adapter的使用
一.Adapter的基本概念 UI控件都是跟Adapter(适配器)打交道的,了解并学会使用这个Adapter很重要, Adapter是用来帮助填充数据的中间桥梁,简单点说就是:将各种数据以合适的形式 ...