文章目录
question?
什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c -o pro
磁盘中生成pro文件,叫做程序。
进程就是程序的一次运行活动,通俗点讲就是程序跑起来,系统中就多了一个进程。
如何查看系统中有哪些进程?
1、使用ps指令查看(比如ps -aux|grep xxx)
2、使用top(类似任务管理器)
什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
Pid=0: 称为交换进程(swapper),作用—进程调度
Pid=1:init进程,作用—系统初始化
编程调用getpid函数获取自身的进程标识符
getppid获取父进程的进程标识符
什么是父进程、什么是子进程?
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
C程序的存储空间是如何分配的?
fork
pid_t fork(void);
fork函数调用成功,返回两次
返回值为0, 代表当前进程是子进程
返回值非负数,代表当前进程为父进程
调用失败,返回-1
父子进程写实拷贝,共享正文段,并不共享存储空间部分,父进程返回值为子进程pid,子进程返回值为0
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int a=10;
printf("father id=%d\n",getpid());
pid = fork();
if(pid>0){
printf("this is father pid=%d\n",getpid());
}else if(pid == 0){
printf("this is child pid=%d\n",getpid());
a=a+10
}
printf("%d\n",a);
return 0;
}
fork创建子进程的一般目的
应用:服务端请求
(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。在网络服务中是常见的-----父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一服务请求到达。demo如下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int data=10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data==1){
pid=fork();
if(pid>0)
{}
else if(pid==0){
while(1){
printf("do net request,pid=%d\n",getpid());
sleep(3);
}
}
}
else{
printf("wait do nothing\n");
}
}
return 0;
}
(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程返回后立即调用exec(后面讲)
vfork
vfork与fork的区别:
关键区别一:
vfork 直接使用父进程存储空间,不拷贝。
关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork(); //固定子进程先运行
if(pid>0){
printf("cnt=%d\n",cnt);
printf("this is father print,pid=%d",getpid());
sleep(1);
}else if(pid == 0){
while(1){
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
进程退出
正常退出:
1、main函数调用return
2、进程调用exit(),标准C库
3、进程调用_exit()或_Exit(),属于系统调用
补充:
1、进程最后一个线程返回
2、最后一个线程调用pthread_exit
异常退出:
1、调用abort
2、当进程收到某些信号时,如ctrl+C
3、最后一个线程对取消(cancellation)请求作出响应
等待子进程
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
status参数:
是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态
父进程等待子进程退出,并收集进程的退出状态,若子进程的退出状态不被收集,变成僵尸进程。
若正常终止子进程返回的状态,则为真,执行WEXITSTATUS(staus)取子进程传送给exit、_exit、_Exit参数的低8位。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int staus=10;
pid = fork();
if(pid > 0){
wait(&staus);
//waitpid(pid,&status,WNOHANG);父进程不阻塞,也产生僵尸进程。
printf("child quit,status=:%d\n",WEXITSTATUS(staus));
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid =%d\n",getpid());
sleep(1);
}
}else if(pid==0){
while(1){
printf("this is child print,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0){
printf("this is father print,pid =%d\n",getpid());
}else if(pid==0){
while(1){
printf("this is child print,pid=%d,my father pid=%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
exec族函数
execl, execlp, execle, execv, execvp, execvpe
功能:
在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
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环境变量,在它所指定的各目录中搜寻可执行文件。
execl、execlp
带l的一类exac函数(l表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。
带p的一类exac函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//int execl(const char *path, const char *arg, ...);
//int execlp(const char *file, const char *arg, ...);
int main()
{
printf("before execl****\n");
// if(execl("/bin/date","date",NULL) == -1)
if(execlp("date","date",NULL) == -1)
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
execvp
带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//int execvp(const char *file, char *const argv[]);
int main(void)
{
printf("before execlp\n");
char *argv[] = {"ps","-l",NULL};
if(execvp("ps",argv) == -1)
{
printf("execvp failed!\n");
}
printf("after execlp\n");
return 0;
}
execle
带e的一类exac函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//int execle(const char *path, const char *arg,..., char * const envp[]);
char *env_init[] = {"AA=aa","BB=bb",NULL};
int main(void)
{
printf("before execle****\n");
if(execle("./bin/echoenv","echoenv",NULL,env_init) == -1)
{
printf("execle failed!\n");
}
printf("after execle*****\n");
return 0;
}
//文件echoenv.c
#include <stdio.h>
#include <unistd.h>
extern char** environ;
int main(int argc , char *argv[])
{
int i;
char **ptr;
for(ptr = environ;*ptr != 0; ptr++)
printf("%s\n",*ptr);
return 0;
}
结果
AA=aa
BB=bb
exec配合fork使用
建一个config.txt
SPEED=3
LENG=0
SCORE=9
LEVEL=5
建一个changeData.c//之前文件那章写过
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
int fdSrc;
char *readBuf=NULL;
if(argc !=2){
printf("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=");
if(p==NULL){
printf("not found\n");
exit(-1);
}
p=p+strlen("LENG=");
*p='5';
lseek(fdSrc,0,SEEK_SET);
int n_write =write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
return 0;
}
gcc changeData.c -o changeData生成可执行文件changeData
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int data=10;
while(1){
prinyf("please input a data\n");
scanf("%d",&data);
if(data==1){
pid=fork();
if(pid>0){
wait(NULL);
}
if(pid==0){
execl("./changeData","changeData","config.txt",NULL);
}
}else{
print("wait,do nothing\n");
}
}
return 0;
}
system函数
int system(const char * string);
system与exec族函数的区别:
1、system()和exec()都可以执行进程外的命令,system是在原进程上开辟了一个新的进程,但是exec是用新进程(命令)覆盖了原有的进程。
2、system()和exec()都有能产生返回值,system的返回值并不影响原有进程,但是exec的返回值影响了原进程。
3、system需要先启动一个shell才能运行指定的命令,调用system函数执行指定命令时,原进程会暂停等待,之后再继续进行;调用exec函数开启新进程后,原进程将被直接关闭。
返回值:
成功,则返回进程的状态值;
当sh不能执行时,返回127;
失败返回-1;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{ //int system(const char * string);
//execl("/bin/date","date",NULL);
if(system("date") == -1){
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
popen函数
FILE popen( const char command, const char* mode )
popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。这个进程必须由 pclose() 函数关闭。
比system在应用中的好处:
可以获取运行的输出结果
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
返回值:如果调用 失败,或者不能分配内存将返回NULL,否则返回标准 I/O 流。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024] = {0};
FILE *fp;
fp = popen("date","r");
int nread = fread(ret,1,1024,fp);
printf("read ret %d byte , ret:%s\n",nread,ret);
return 0;
}