2024年1月15日发(作者:)

ESD_DAY032_Elvis UNIX/LINUX

系统下的C高级编程 A.N.E.K

1.进程管理

1.1 进程的终止

_exit()/_Exit()函数:

功能:立即终止当前正在运行的进程。

格式:#include

void _exit(int status);

#include

void _Exit(int status);

注意:_exit()和_Exit()函数功能上完全相同;在终止进程时,会自动关闭属于该进程的文件描述 参数值会被返回给父进程作为该进程终止时的退出状态信息,父进程需要调用wait系列函数来符,会给该进程的父进程发送SIGCHLD信号,该进程的所有子进程会重新认定新的父进程为init;

获取该数据。

返回值:无返回值

exit()函数:

功能:引起正常进程的终止。

格式:#include

void exit(int status);

注意:将参数status&0377之后的数据返回给当前进程的父进程

自动调用所有被atexit()和on_exit()函数注册过的函数,一般用于处理善后工作,然后终止进程。

返回值:无返回值

atexit()函数:

功能:对参数指定的函数进程注册。

格式:#include

int atexit(void(*function)(void));

注意:所有被该函数注册过的函数会在正常终止时被调用(调用exit函数和执行main函数中的返回值:成功返回0,失败返回非零。

return)。

wait()函数

功能:主要用于挂起当前正在调用的进程,直到有一个该进程的子进程终止为止,可用于回收子进程,格式:#include

#include

pid_t wait(int *status);

注意:参数status指针表示获取子进程的终止状态并放到status这个变量中;如果参数为空时,表示不关心子进程的退出状态;如果参数不为空时,该函数会将进程的状态信息存储在参数指定的int类型空间中,解析该状态需要借助以下带参宏,传递给宏的参数必须是整数自己,而不是指向它的指针:

WIFEXITED(*status):如果子进程正常终止(exit()或_exit()或return),则返回true;(wait if

当有多个子进程时,随机回收一个。

嵌入式培训笔记 1 2015-09-14

ESD_DAY032_Elvis

exited)

UNIX/LINUX

系统下的C高级编程 A.N.E.K

WEXITSTATUS(*status):获取子进程的退出码,所谓的退出码就是子进程调用exit/_exit/_Exit函数的参数或者main函数的返回值的低8位,因此传递给这三个函数的参数和main函数的返回值最好不要超过【-128,127】;(wait exit status)

WIFSIGNALED(*status):判断子进程是否异常终止;如果是则通过WTERMSIG(status)宏获取返回值:成功返回终止的子进程的进程号,失败返回-1。

导致子进程异常终止的信号。

1.2 父进程在创建若干个子进程后调用wait函数

a.若所有子进程都在运行,则父进程阻塞(可视为睡眠),直到有子进程终止

b.若有一个子进程终止,则返回该子进程的PID并通过status参数(若非空)则输出其终止状态;

c.若没有需要等待子进程,则返回-1,置errno为ECHILD(可作为多个子进程全部返回的标志),表示所有子进程回收完毕;

d.如果有一个子进程在wait函数被调用之前已经终止,则将处于僵尸状态,此时调用wait函数会立即返回,同时取得该子进程的终止状态并通知父进程回收其资源;

由此可见wait函数主要完成三个任务:

A.阻塞父进程的运行,直到子进程终止再继续,停等同步(即等待子进程处理结束);

B.获取子进程的PID和终止状态,使父进程了解哪个子进程终止了及其终止原因;

C.为子进程收尸,防止大量的僵尸进程存在于内核而耗费系统资源;

1.3 status的使用

int status;//定义变量以装载子进程退出状态

pid_t pid=wait(&status);//阻塞父进程,等待子进程结束;

if(-1==pid)

{

}

if(WIFEXITED(status))

else

printf(“%d子进程正常终止,退出码%dn“,pid,WEXITSTATUS(status));

perror(“wait”);

exit(EXIT_FAILURE);

嵌入式培训笔记 2 2015-09-14

ESD_DAY032_Elvis

UNIX/LINUX

系统下的C高级编程 A.N.E.K

printf(“%d子进程异常终止,终止信号%dn”,pid,WTERMSIG(status));

****使用wait函数及其相关宏回收子进程并判断其退出状态********************

#include

#include

#include

#include

int main()

{

pid_t pid=fork();

if(-1==pid)

{

perror("fork");

return -1;

}

if(0==pid)

{

int status=0x12345678;

printf("这是子进程%d,将以%#x状态退出n",getpid(),status);//#x表示在打印时显示0x

//return status;

//exit(status);

//_exit(status);

_Exit(status);//return/exit/_exit/_Exit结果相同

}

printf("这是父进程,等待子进程...n");

int status;

pid=wait(&status);

printf("父进程发现%d进程以%#x状态退出n",pid,WEXITSTATUS(status));

return 0;

}

1.4 waitpid函数

waitpid函数

使用时引入头文件sys/types.h,sys/wait.h

功能:等待并回收任意或特定的子进程,。

格式:#include

#include

pid_t waitpid(pid_t pid,int*status,int options);

嵌入式培训笔记 3 2015-09-14

ESD_DAY032_Elvis

UNIX/LINUX

系统下的C高级编程 A.N.E.K

注意:第一个参数:取小于-1的数,表示等待并回收特定进程组(一群子进程的父进程的PID,由-PID标识)的任意子进程;等于-1,等待并回收任意的子进程,相当于wait函数;等于0,等待并回收与调用进程同进程组的任意子进程;大于0的数,等待并回收特定的子进程(pid标识);

第二个参数:status,输出子进程的终止状态,可置NULL;

第三个参数:options:取0,阻塞模式,若所等子进程仍在运行,则阻塞,直到其终止;取WNOHANG,返回值:成功返回终止的子进程的进程号,失败返回-1。

非阻塞模式,若所等待子进程仍在运行,并不阻塞,而是立即返回0;

****使用waitpid函数等待并回收指定的子进程*****************************

#include

#include

#include

int main()

{

pid_t pids[3];//创建一个数组来存放创建多个子进程

int i=0;

for(i=0;i<3;i++)//使用循环创建多个子进程

{

pids[i]=fork();

if(-1==pids[i])

{

perror("fork");

return -1;

}

if(0==pids[i])

{

printf("子进程:我是子进程%d,我要退出了n",getpid());

return 0;

}

}

for(i=0;i<3;i++)

{

printf("父进程:我要等待%d进程...n",pids[i]);

pid_t pid=waitpid(pids[i],NULL,0);//根据pids[i]来回收指定的子进程

if(-1==pid)

{

perror("waitpid");

return -1;

}

printf("父进程:发现%d进程退出了n",pid);

嵌入式培训笔记 4 2015-09-14

ESD_DAY032_Elvis

}

return 0;

}

UNIX/LINUX

系统下的C高级编程 A.N.E.K

****使用waipid函数的非阻塞选项回收子进程**********************************

#include

#include

#include

#include

int main()

{

pid_t pid=fork();

if(-1==pid)

{

perror("fork");

return -1;

}

if(0==pid)

{

printf("子进程:采集数据开始...n");//模拟采集

sleep(5);

printf("子进程:采集数据完成n");

return 0;

}

//pid=wait(NULL);

//pid=waitpid(-1,NULL,0);//和wait相同

for(;;)//使用循环不断检测子进程是否结束

{

pid=waitpid(-1,NULL,WNOHANG);

if(-1==pid)

{

perror("waitpid");

return -1;

}

else if(0==pid)//所有的子进程都在运行状态,做其他处理

{

printf("主进程空闲处理n");

}

else//所有的子进程都已经结束,退出循环

嵌入式培训笔记 5 2015-09-14

ESD_DAY032_Elvis

{

UNIX/LINUX

系统下的C高级编程 A.N.E.K

printf("主进程:子进程已经终止n");

break;

}

}

printf("主进程:分析数据...n");

return 0;

}

1.5 创建新的进程

exec函数:创建的新的进程的PID调用函数相同。

功能:创建一个新的进程,取代调用进程。

格式: #include

int execl(const char *path, const char *arg, ...);//l表示可变长参数表

注意:第一个参数:要启动程序的路径;

第二个参数:程序或命令名称,放在argv[0]中;

变长参数:程序或命令的选项,放在argv[1]~argv[i],i代表选项的个数中;后面必须以NULL结束,例如:exec(“bin/ls“,”ls“,”-l“,NULL),其中path=bin/ls,arg[0]=”ls”,arg[1]=”-l”;至少有一个;

返回值:成功则无法返回(因为exec调用会以跳转到新进程的入口地址为调用进程的结束,而刚刚运行的代码是不会存在于新进程的地址空间中的),失败返回-1,并设置errno;;

------------------------------------------------------------------------------------------

功能:创建一个新的进程,取代调用进程。

格式: #include

int execlp(const char *file, const char *arg, ...);//p表示自动搜索PATH路径

注意:第一个参数:要启动程序的名字;系统会根据path和名字的拼接自动去找;

第二个参数:程序或命令名称,放在argv[0]中;

变长参数:程序或命令的选项,放在argv[1]~argv[i],i代表选项的个数中;后面必须以NULL结束,例如:exec(“ls“,”ls“,”-l“,NULL),其中path=bin/ls,arg[0]=”ls”,arg[1]=”-l”;

返回值:成功则无法返回,失败返回-1,并设置errno;;

功能:创建一个新的进程,取代调用进程。

格式: #include

int execle(const char *path, const char *arg,..., char * const envp[]);//e表示带环境变量

注意:与fork和vfork函数不同,exex函数不是创建调用进程的子进程,而是创建一个新的进程取返回值:成功则无法返回,失败返回-1,并设置errno;

------------------------------------------------------------------------------------------

int execv(const char *path, char *const argv[]);//v表示字符指针数组

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]);

类似前面三个函数,只是将命令行参数直接放到了argv[]数组中;

代调用进程。新的进程用自己的全部地址空间,覆盖调用进程的地址空间,进程PID保持不变。

****实验程序:打印命令行参数和环境变量,生成argenv程序,供后面使用********

嵌入式培训笔记 6 2015-09-14

ESD_DAY032_Elvis UNIX/LINUX

系统下的C高级编程

#include

#include

//打印命令行参数

void printArgv(char*argv[])

{

printf("---------命令参数--------n");

while(argv&&*argv)

{

printf("%sn",*argv++);

}

printf("--------------------------n");

}

//打印环境变量

void printEnvp(char*envp[])

{

printf("---------环境变量---------n");

while(envp&&*envp)

{

printf("%sn",*envp++);

}

printf("--------------------------n");

}

int main(int argc,char*argv[],char*envp[])

{

printArgv(argv);

printEnvp(envp);

return 0;

}

****使用exec函数家族创建新进程argenv*******************************

#include

#include

int main()

{

printf("创建一个新进程n");//可以打印出来

//创建目标进程argenv

/*i(1)nt res=execl("./argenv","argenv","hello","world","hello world",NULL);

if(-1==res)

{

嵌入式培训笔记 7

A.N.E.K

2015-09-14

ESD_DAY032_Elvis UNIX/LINUX

系统下的C高级编程

perror("execl");

return -1;

}*/

/*(2)//定义字符指针数组来存放命令行参数

char*argv[]={"argenv","hello","world","hello world",NULL};

//启动argenv进程

int res=execv("./argenv",argv);

if(-1==res)

{

perror("execv");

return -1;

}*/

/*(3) //定义字符指针数组存放环境变量

char*envp[]={"NAME=NARUKANA","AGE=17500",NULL};

//创建新进程argenv

int res=execle("./argenv","argenv","hello","world","hello world",NULL,envp);

if(-1==res)

{

perror("execle");

return -1;

}*/

/* (4)//定义字符指针数组来存放命令行参数

char*argv[]={"argenv","hello","world","hello world",NULL};

//定义字符指针数组存放环境变量

char*envp[]={"NAME=NARUKANA","AGE=17500",NULL};

int res=execve("./argenv",argv,envp);

if(-1==res)

{

perror("execve");

return -1;

}*/

/*(5)int res=execlp("argenv","argenv","hello","world","hello world",NULL);

if(-1==res)

{

perror("execlp");

return -1;

}*/

//(6)定义字符指针数组来存放命令行参数

嵌入式培训笔记 8

A.N.E.K

2015-09-14

ESD_DAY032_Elvis UNIX/LINUX

系统下的C高级编程 A.N.E.K

char*argv[]={"argenv","hello","world","hello world",NULL};

int res=execvp("argenv",argv);

if(-1==res)

{

perror("execvp");

return -1;

}

printf("创建新进程成功n");//若argenv进程创建成功,则无法打印

//因为原来的进程已经被新创建的argenv进程取代

return 0;

}

其实只有execve是真正的内核函数,其他五个exec函数只不过是对该函数简单包装。

1.6 调用exec函数不仅改变调用进程的地址空间和进程映像,调用进程的属性也会发生变化:

(1)任何出于阻塞状态的信号都会丢失;

(2)被设置为捕获的信号会还原为默认操作;

(3)有关线程的设置都会还原到缺省值(复位);

(4)有关进程的统计信息也会复位(如进程消耗的时间等);

(5)与原进程内存相关的任何数据都会丢失,包括内存映射文件;

(6)标准库在用户空间维护的一切数据结构,如atexit或on_exit函数注册的退出处理函数,都会丢失;

但是也有属性会被新进程继承下来,如PID,PPID,实际用户ID,实际组ID,优先级,以及文件描述符表(即用原来进程创建的文件描述符在现在的新进程中也能直接使用)。

1.7 调用exec函数固然可以创建出新的进程,但是新的进程会取代原来的进程。如果既希望创建新的进程,同时又希望原来的进程继续存在,则可以采用fork+exec模式,即在fork产生的子进程中调用exec函数,新进程取代了子进程,但父进程依然存在。

如:pid_t pid=fork();//创建子进程

嵌入式培训笔记 9 2015-09-14

ESD_DAY032_Elvis

if(-1==pid){. . .}

if(0==pid)

{

}

UNIX/LINUX

系统下的C高级编程 A.N.E.K

int res=execl(“/bin/ls”,”ls”,”-l”,NULL);//在子进程中创建新进程

if(-1==res){. . .}

1.8 system函数

使用时引入头文件stdlib.h

功能:创建command参数指定的进程并返回命令所创建的进程的终止状态。

格式: #include

int system(const char *command);

注意:一个参数:shell命令行字符串,可以为NULL,此时表示检测当前shell是否可用;

返回值:成功返回command进程的终止状态,失败返回-1,并设置errno。

****使用system创建新进程***********************************************

#include

#include

#include

#include

int main()

{

int status=system(NULL);

if(-1==status)

{

perror("status");

return -1;

}

//检测shell是否可用,返回零表示不可用

if(!status)

{

printf("shell不可用n");

return -1;

}

status=system("ls -l");

if(-1==status)

{

perror("system");

return -1;

}

//system返回的状态信息,需要解析为退出码

printf("ls进程的退出码:%dn",WEXITSTATUS(status));

嵌入式培训笔记 10 2015-09-14

ESD_DAY032_Elvis

return 0;

}

UNIX/LINUX

系统下的C高级编程 A.N.E.K

2信号处理

2.1 基本概念

1)中断--中止当前正在执行的程序,转而处理其他任务。

硬件中断:来自硬件设备的中断。

软件中断:来自其他程序的中断。

2)异步--中断的发生在程序设计看来完全无法预见,对中断的处理与程序正在执行的任务也没有固定的时序和时机。

3)信号--在Unix/Linux系统中处理异步中断的方式。

4)常见的信号

SIGHUP--对应信号值1,连接断开信号,默认处理:终止;

SIGINT--对应的信号值2,终端中断符(Ctrl+C),默认处理:终止;

SIGQUIT--对应信号值3,终端退出符(Ctrl+),默认动作,终止+core(core文件);

SIGABRT--对应信号值6,异常终止,手动abort函数可发送,终止+core;

SIGKILL--对应信号9,杀灭进程,默认处理:终止。不能被忽略和捕获;

SIGTERM--对应信号值15,终止进程,可以被捕获;

SIGSEGV--对应信号值11,段错误,默认动作:终止+core;

SIGCHLD--对应信号值17,子进程状态改变,默认动作:忽略;

使用kill -l命令查看系统支持的信号,其中1-31是非实时信号,不可靠信号;34-64(RT)是实时信号,可靠信号,共62个。

嵌入式培训笔记 11 2015-09-14