linux系统编程--进程
1. 进程的相关概念
1.1 什么是进程,什么是程序,有什么区别?
程序是静态的概念,gcc xxx.c -o pro 磁盘中生成pro文件,叫做程序。
进程是程序动态运行的过程,进程是程序的一次运行活动(程序的执行过程),通俗点意思是程序跑起来了,系统就多了一个进程。
1.2 如何查看系统中有哪些进程?
a.使用ps指令查看ps -aux|grep xxx
在实际工作中,配合grep来查找程序中是否存在某一个进程。
ps -aux//查看系统中所有进程
ps -aux|grep xx//查找有关xx的进程方便查找(从所有进程中过滤)
b.使用top指令查看,类似windows任务管理器
1.3 什么是进程标识符
1.3.1 基本概念
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
Pid=0: 称为交换进程(swapper)
作用—-进程调度
Pid=1:init进程
作用—-系统初始化
1.3.2 基本格式
查看相关信息:man 2 getpid
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
1.3.2.1 编程查看
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid();
printf("my pid is %d\n",pid);
while(1);
return 0;
}
1.3.2.2 编程结果
右边相当于linux的任务管理器,使用指令:top
来查看
1.3.3 函数功能
函数原型 | pid_t getpid(void) |
---|---|
功能 | 获取当前进程的进程号 |
返回值 | 成功:获取进程号 |
函数原型 | pid_t getppid(void) |
---|---|
功能 | 获取当前进程的父进程的进程号 |
返回值 | 成功:获取父进程的进程号 |
pid_t 的实质是int类型的。
getpid()返回当前进程标识码,getppid()返回父进程标识。
1.4 什么叫父进程,什么叫子进程?
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
1.5 C程序的存储空间是如何分配?
2. 创建进程函数fork的使用
查看fork相关信息:man 2 fork
2.1 基本格式
#include <unistd.h>
pid_t fork(void);
---------------------------------------------------------------------------------------------
fork函数调用成功,返回两次
返回值为0 ,代表当前进程为子进程
返回值为非负数 ,代表当前进程为父进程
调用失败 ,返回-1
2.2 编程实战
2.2.1 初探fork()
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid();
//pid_t fork(void);
fork();
printf("my pid is %d\n",pid);
return 0;
}
发现printf()执行了两次,这是为啥腻???
其实是fork()的作用,程序是从上到下执行的,当执行到fork()时,此时程序已经由一个进程转变为两个进程在跑,所以导致两个进程程序都执行了prinf()。 搜嘎斯乃~~
2.2.2
那我们可进行相关代码操作,区分父子进程
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid();//父进程
//pid_t fork(void);
fork();//建立父子进程
/*若下面printf()中的pid相同,则为父进程
若下面printf()中的pid不同,则为子进程*/
printf("my pid is %d,current pro id:%d\n",pid,getpid());
return 0;
}
2.2.3
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid();//父进程pid
//pid_t fork(void);
fork();//创建父子进程pid
if(pid == getpid()){//进入父进程
printf("this is father print,father pid = %d\n",getpid());
}else{ //进入子进程
printf("this is child print,child pid = %d\n",getpid());
}
return 0;
}
2.2.4
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid1;
pid_t pid2;
pid1 = getpid();//getpid():获取当前进程的pid号--父进程
printf("before fork pid1=%d\n",pid1);
fork();//fork后会创建两个进程,分别为父进程和子进程,两个进程都会执行fork后的代码程序。
pid2 = getpid();//获取当前进程的pid
printf("after fork pid2=%d\n",pid2);
if(pid1 == pid2){//如果父进程等于当前进程的pid号则该进程为父进程
printf("this is father print\n");//父进程
}else{
printf("this is child print,child pid=%d\n",pid2);//子进程
}
return 0;
}
----------------------------------------------------------------------
输出结果:
before fork:pid1 = 40444
//父进程执行
after fork pid2 = 40444
this is father print
//子进程执行
after fork pid2 = 40445
this is child print,child pid = 40445
2.2.5
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
printf("father:id = %d\n",getpid());//父进程pid
pid = fork();//创建父子进程
if( pid > 0 ){//父进程返回值:非负数
printf("this is father print,father pid = %d\n",getpid());
}
if( pid == 0){//子进程返回值:0
printf("this is child print,child pid = %d\n",getpid());
}
return 0;
}
--------------------------------------------------------------------
运行结果:
father:id = 40508
this is father print,father pid = 40508
this is child print,child pid = 40509
2.2.6
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid1;
pid_t pid2;
pid_t retpid;
pid1 = getpid();//getpid():获取当前进程的pid号--父进程
printf("before fork pid1=%d\n",pid1);
retpid = fork();//fork后会创建两个进程,分别为父进程和子进程,两个进程都会执行fork后的代码程序。
printf("retpid = %d\n",retpid);
pid2 = getpid();
printf("after fork pid2=%d\n",pid2);
if(pid1 == pid2){//如果父进程等于当前进程的pid号则该进程为父进程
printf("this is father print,retpid = %d\n",retpid);
}else{
printf("this is child print,child pid=%d,retpid = %d\n",pid2,retpid);
}
return 0;
}
================================================================
输出结果:
before fork:pid1 = 40723//父进程pid
retpid = 40742//在进程中fork返回的值子进程pid
after fork:pid2 = 40723
this is father print,retpid = 40734//父进程中子进程pid
retpid = 0//进入子进程
after fork pid2 = 40724//子进程Pid与父进程的fork返回值一致
this is child print,child pid = 40724,retpid = 0
在父进程时fork()返回子进程pid,而在子进程时fork()返回0
3. 进程创建发生了什么事情?
在早期的Linux内核中,新进程会将代码段、数据段、bss段、堆、栈、命令行参数和环境变量等全部拿来拷贝一份,放入新进程中执行,而随着Linux内核的更新,当新进程被创建的时候,采用写时拷贝的方式运行新进程,在新进程中发生改变的变量新进程会将其拷贝一份作为自己的数据,而其他未被触及到的内容则于父进程进行共享(即后面子进程没有对变量数据做改变的话,则采用共享内存空间方式;只有在对变量数据做改变时,子进程的地址空间才会拷贝一份变量数据过来)。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int data = 20;
printf("father:id = %d\n",getpid());
pid = fork();//创建父子进程
if( pid > 0 ){//父进程返回值:非负数
printf("this is father print,father pid = %d\n",getpid());
}
if( pid == 0){//子进程返回值:0
printf("this is child print,child pid = %d\n",getpid());
data = data + 200;
}
printf("data = %d\n",data);
return 0;
}
-------------------------------------------------------------------
运行结果:
father:id = 40774
this is father print,father pid = 40774
data = 10
this is child print,child pid = 40775
data = 220//新进程写时拷贝了data数据作为自己的变量,与父进程区分开来,而其他的变量则与父进程共享.
4. 创建新进程的实际应用场景及fork总结
4.1 fork创建一个子进程的一般目的
1.一个父进程希望复制自己,使父子进程同时执行不同的代码段,在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int data;
while(1){
printf("please input a data:\n");
scanf("%d",&data);
if(data == 1){//界面输入1
pid = fork();//每输入一次1,都会创建一个父子进程,主要用创建的子进程来执行相关操作
if( pid > 0 ){//父进程返回值:非负数
}
if( pid == 0){//子进程返回值:0
while(1){
printf("do net conncect,pid = %d\n",getpid());
sleep(3);//休眠3秒,防止刷屏
}
}
}else{
printf("do nothing!\n");
}
}
return 0;
}
运行结果:
父子进程两者都是同时运行,父进程一直监测用户输入,当有客户端来时,创建子进程服务于客户端。父进程只检测什么也不干。这里我们可以看出当未创建服务端时,父进程一直在等待输入,当服务端来临时,立刻创建一个子进程,同时父进程也在运行等待新的输入,最后我们输入两个1意味着创建了两个子进程,这两个子进程同时运行。
5. vfork创建进程
5.1 vfork与fork的区别
- 区别一:vfork 直接使用父进程存储空间,不拷贝。
- 区别二:vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
5.2对于fork的使用的解析
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork();//创建父子进程
if( pid > 0 ){//父进程返回值:非负数
while(1){
printf("this is father print,father pid = %d\n",getpid());
slepp(1);
}
}
if( pid == 0){//子进程返回值:0
while(1){
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
}
}
return 0;
}
运行结果:
可见:fork的创建的父子进程同时运行,在争夺CPU的资源 谁先执行取决于进程的调度
5.3 vfork的使用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();//vfork保证子进程先运行,子进程退出后,父进程运行
if( pid > 0 ){//父进程返回值:非负数
while(1){
printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
printf("this is father print,father pid = %d\n",getpid());
slepp(1);
}
}
if( pid == 0){//子进程返回值:0
while(1){
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
运行结果:
由此可见,当使用vfork()时,子进程的内容也会影响父进程的内容,注意:当使用fork()时,父子进程互不干涉。
6. 进程的退出
6.1 正常退出
1. Main函数调用return
2. 进程调用exit(),标准c库
3. 进程调用_exit()或者_Exit(),属于系统调用
补充:
1. 进程最后一个线程返回
2. 最后一个线程调用pthread_exit
6.2异常退出
1. 调用abort(放弃当前进程)
2. 当进程收到某些信号时,如ctrl+C
3. 最后一个线程对取消(cancellation)请求做出响应
注意:但不管是哪种退出方式,系统最终都会执行内核中的某一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。
6.3 基本格式
查看相关信息:man 2 exit
#include <stdlib.h>
void exit(int status);
7. 父进程等待子进程退出
7.1 父进程等待子进程退出的目的
进程创建子进程的目的就是能够让子进程去响应某个事件,并且做出相应的处理。如果父进程关心子进程对于事件的处理情况,那么父进程则可收集子进程的退出状态来判断。
7.2 僵尸进程
对于父子进程来说,父进程在忙,子进程结束了,但无人回收,这样就造成了“死亡”的子进程一直占用资源,这个时候的子进程被称为僵尸进程(Z+)。
为了解决这个问题,最初的思路是:让父进程停下,等待子进程执行完,然后回收子进程,清理掉的子进程ID。
7.3 基本格式
查看相关信息:man 2 wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int* status);
------------------------------------------------------------------------------
status参数:是一个整形数指针,非空:子进程退出状态放在它所指向的地址中; 空:不关心退出状态
7.4 编程实战
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();//创建两个进程
if( pid > 0 ){//父进程返回值:非负数
while(1){
wait(NULL);//等待子进程运行完收集后再运行父进程
printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
printf("this is father print,father pid = %d\n",getpid());
slepp(1);
}
}
if( pid == 0){//子进程返回值:0
while(1){
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
运行结果:
此时子进程不再是僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();//vfork保证子进程先运行,子进程退出后,父进程运行
if( pid > 0 ){//父进程返回值:非负数
while(1){
wait(&status);//等待子进程运行完收集后再运行父进程
printf("child quit.child status = %d\n",WEXITSTATUS(status));//返回子进程结束后的状态码3
printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
printf("this is father print,father pid = %d\n",getpid());
slepp(1);
}
}
if( pid == 0){//子进程返回值:0
while(1){
printf("this is child print,child pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3){
exit(3);//子进程结束后返回状态码3
}
}
}
return 0;
}
7.5 waitpid基本格式
#include <sys/types.h>
#include <sys/wait.h>
pid_ t waitpid(pid_t pid, int *status, int options);
-------------------------------------------------------------------------------------
参数说明:
pid == -1 等待任一子进程。就这一方面而言,waitpid与wait等效
pid > 0 等带其进程ID与pid相等的子进程
pid == 0 等待其组ID等于调用进程组ID的任一子进程
pid < -1 等待其组ID等于pid绝对值的任一子进程
status 是一个整形数指针,非空:子进程退出状态放在它所指向的地址中。空:不关心退出状态。
options:
WCONTINUED 若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态
WNOHANG(多有) 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNTRACED 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程
7.6 wait和waitpid的区别:
wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞。
7.7waitpid编程实战
注意:fork()
返回子进程pid(>0),进入父进程程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();//创建父子进程
if( pid > 0 )//父进程返回值:子进程pid (非负数)
{
//pid--子进程pid
//$status--退出值
//WNOHANG--不挂起的方式
waitpid(pid,&status,WNOHANG);
printf("child quit.child status = %d\n",WEXITSTATUS(status));//返回子进程结束后的状态码3
while(1){
printf("cnt = %d\n",cnt);
printf("this is father print,father pid = %d\n",getpid());//获取父进程pid
sleep(1);
}
}
else if( pid == 0){//进入子进程 返回值0
while(1){
printf("this is child print,child pid = %d\n",getpid());//获取子进程pid
sleep(1);
cnt ++;
if(cnt == 5){
exit(3);
}
}
}
return 1;
}
注意:此时子进程被收回,但是仍成为了僵尸进程
7.8 孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
7.8.1 编程实践
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if( pid > 0 )
{
printf("this is father print,father pid = %d\n",getpid());
}
else if( pid == 0){
while(1){
printf("this is child print,child pid = %d,my father pid = %d\n",getpid(),getppid());//getppid()获取父进程的!!!
sleep(1);
cnt ++;
if(cnt == 5){
exit(3);
}
}
}
return 1;
}
可见,当父进程运行一次时,结束父进程,而子进程需要运行5次才能结束进程,但是当父进程结束时,子进程成为孤儿进程,子进程被父进程1420收留。
8.exec族函数
8.1exec族函数的作用
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
8.2 exec族成员
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]); //不做重点
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]); //不做重点
返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
8.3 execl编程
主程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("before execl\n");
//没有带路径且arg必须以NULL结束
if(execl("./echoarg","echoarg","abc",NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
{
printf("execl failed!\n");
}
printf("after execl\n");//当execl成功调用时,该行代码不会执行
return 0;
}
待执行程序:
//echoarg.c
//编译时gcc echoarg.c -o echoarg
#include <stdio.h>
int main(int argc,char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n",i,argv[i]);
}
return 0;
}
运行结果:
before execl
argv[0]: echoarg
argv[1]: abc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("before execl\n");
//没有带路径且arg必须以NULL结束
if(execl("/bin/ls","ls",NULL,NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
{
printf("execl failed!\n");
}
printf("after execl\n");//当execl成功调用时,该行代码不会执行
return 0;
}
运行结果:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("before execl\n");
//没有带路径且arg必须以NULL结束
if(execl("/bin/ls","ls","-l",NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
{
printf("execl failed!\n");
}
printf("after execl\n");//当execl成功调用时,该行代码不会执行
return 0;
}
运行结果:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("this pro get system date:\n");
//没有带路径且arg必须以NULL结束
if(execl("/bin/date","date",NULL,NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
{
printf("execl failed!\n");
}
printf("after execl\n");//当execl成功调用时,该行代码不会执行
return 0;
}
-----------------------------------------------------------
运行结果:
this pro get system date:
xxxxxxxxxxxx(虚拟机上的时间)
8.4 execlp的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("this pro get system date:\n");
//没有带路径且arg必须以NULL结束
//execlp第一个参数不需要绝对路径
//execlp会根据环境变量去寻找第一个参数
if(execlp("ps","ps",NULL,NULL) == -1)
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
8.5 execvp使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("this pro get system date:\n");
char *argv = {"ps",NULL,NULL};
if(execvp("ps",argv ) == -1)
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
8.6 execv使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
printf("this pro get system date:\n");
char *argv = {"ps",NULL,NULL};
//第一个参数戴绝对路径
if(execv("/bin/ps",argv ) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
{
printf("execl failed!\n");
}
printf("after execl\n");//当execl成功调用时,该行代码不会执行
return 0;
}
8.7 exec配合fork使用:(在一个进程中调用另一个程序)
实现功能,当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。
//调用函数
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
int data;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid == 0){
execl("./changeData","changeData","config.txt",NULL);
}
}else{
printf("wait,do nothing!\n");
}
}
return 0;
}
//被调用函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fdSrc;
char *readBuf = NULL;
if(argc != 2){
printf("param error!\n");
exit(-1);
}
fdSrc = open(argv[1],O_RDWR);
int size = lseek(fdSrc, 0, SEEK_END);
lseek(fdSrc, 0, SEEK_SET);
readBuf = (char*)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc, readBuf, size);
char *p = strstr(readBuf, "LENG=");
p = p + strlen("LENG=");
*p = '5';
lseek(fdSrc, 0, SEEK_SET);
int n_write = write(fdSrc, readBuf, strlen(readBuf));
close(fdSrc);
return 0;
}
运行结果:
原本config.txt内容:
SPEED=3
LENG=1
SCORE=9
LEVEL=5
-----------------------------------------------------------------------------------------------
修改后内容:
SPEED=3
LENG=5
SCORE=9
LEVEL=5
9. system函数
9.1 基本概念
system 函数类似于exec 族函数,我们用fork函数创建新进程后,也能在新进程中调用system 去执行另外一个程序。当进程调用system 函数时,该进程被替换为新程序。与 exec 族函数不同的一点是,system 函数调用完一个程序后,还会返回原来的主程序继续执行完。
9.2 基本格式
#include <stdlib.h>
int system(const char *command);
--------------------------------------------------------------------
参数讲解:
const char *command:命令字符串
system()函数的返回值如下:
成功,则返回进程的状态值; 当sh不能执行时,返回127; 失败返回-1;
9.3 system源码
#include
#include
#include
#include
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork())<0){
status = -1;
}else if(pid == 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-exit(127); //子进程正常执行则不会执行此语句
}
else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTER){
status = -1;
break;
}
}
}
return status;
}
当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的 pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring,(char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个 shell进程,这个shell的参数是cmdstring,就是system接受的参数。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。
9.4 system编程实战
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(system("ps") == -1)//即使成功调用,仍然会执行if后的代码
{
printf("execl failed!\n");
perror("why");//该函数会再why后加冒号(:)再加原因,方便得知错误之处
}
printf("after execl\n");//仍然会运行该程序,与execl的不同之处!!!
return 0;
}
10. popen函数
10.1 基本概念
popen函数比system函数在应用中的好处就是可以获取到运行的输出结果。(system函数获取不了运行的输出结果,原因是system函数封装的就是exce函数,成功运行的话 不会返回)
10.2 基本格式
#include “stdio.h”
FILE popen( const char *command, const char* mode )
---------------------------------------------------------------
参数讲解:
command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。
这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。
如果 type 是 “r” 则文件指针连接到 command 的标准输出;
如果 type 是 “w” 则文件指针连接到 command 的标准输入。
如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断。
int pclose (FILE* stream)
参数说明:
stream:popen返回的文件指针
返回值:
如果调用失败,返回 -1
10.3 popen编程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024] = {0};
FILE *fp;
fp = popen("ps","r");
int nread = fread(ret,1,1024,fp);
printf("read %d byte,ret=%s\n",nread,ret);
pclose(fp);
return 0;
}
运行结果: