1.什么是程序什么是进程:
答:程序是静态概念,编译代码形成的a.out就是程序;进程是程序的一次运行活动,是动态概念,一个程序跑起来了系统中就多一个进程。
2.如何查看系统中的进程:
答:使用ps指令来看;ps -aux|grep init 查看进程并通过grep()指令过滤出带有init字段的进程(精准查看)。使用top指令,相当于windows的任务管理器,显示进程和其运行时的内存占用率。
3.什么是进程标识符:
答:每一个进程都有一个非负整数表示唯一ID,叫做PID类似身份证,PID=0为系统默认的交换进程作用是进程调度。PID=1为系统默认的的init进程,作用是系统初始化。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid= getpid(); //用getpid()获得进程pid的代码,getppid()为获得父亲进程pid的指令。
printf("my pid is:%d\n",pid);
return 0;
}
4.什么是父进程什么是子进程:
答:如果进程A创建了进程B,那么A就叫父进程B就叫子进程。
5.C程序存储空间如何分配:
*可执行程序包括BSS段、数据段、代码段。
*在类UNIX系统下可使用size命令查看可执行文件的段大小信息。如size a.out
*数据段:存放已初始化的全局变量和静态变量,数据段属于静态内存分配。
*BSS段:(Block Started by Symbol)存放未初始化的全局变量和静态变量。
BSS段的数据是可读写的,BSS段并不占用可执行文件的大小。在使用BSS段之前BSS段会自动初始化为0。所以,未初始的全局变量和静态变量在程序执行之前已经是0了。BSS段属于静态内存分配。
包含数据段和BSS段的整个区段通常称为数据区。
*代码段:用来存放程序的代码内存空间。它的大小在程序运行前就已经确定了,并且该区域只能读不能写。在代码段中,也有可能包含了一些只读的常数变量,例如字符串常量等。
代码段和数据段在编译时已经分配了空间,而BSS段则在程序被调入内存后才分配的。因此BSS段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。需要存放在程序文件中的只有代码段和数据段(存放已初始化的全局变量和静态变量)的内容。
程序编译后生成的目标文件至少含有这三个段,这三个段的大致结构图如下所示:
可执行程序在运行时会多出两个区域:栈区和堆区
*栈区:由操作系统自动分配和释放 ,存放函数的参数值,局部变量的值等。每当一个函数被调用时,该函数的返回类型和一些调用的信息也会被存放到栈中。然后这个被调用的函数再为它的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
*堆区:用于动态分配内存(入malloc函数开辟空间),位于BSS和栈中间的地址区域。由程序员亲自用malloc()申请分配和用free()释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的 malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。
什么是内存泄漏:
内存泄漏是指你向系统申请分配内存进行使用(malloc),然后系统在堆内存中给这个对象申请一块内存空间,但当我们使用完了却没有回归系统(free),导致这个不使用的对象一直占据内存单元,造成系统将不能再把它分配给需要的程序。一次内存泄漏的危害可以忽略不计,但是内存泄漏堆积则后果很严重,无论多少内存,迟早会被占完,造成内存泄漏。
6.使用fork函数创建一个进程:
pid_t fork(void);函数调用不需要传参;调用fork生成的进程叫子进程,调用成功执行两次,有两个返回值,返回值为0代表当前为子进程,返回值为非负数代表当前为父进程,父进程的fork返回值为子进程的PID号(因为在一个父进程下会有很多子进程,无法用一个函数使父进程得到所有子进程的PID),调用失败返回-1。在子进程中调用getppid()可以获得父进程的PID,在fork后,子进程是父进程的副本,将父进程的数据空间,栈,堆都拷贝下来,子进程对数据的改变只在拷贝的副本中,不改变父进程内的数据,父子进程不享有共同存储空间。
由于fork和exec的配合,子进程不全执行父进程的所有数据, 后面改良后使用写时复制(COW技术),数据空间,栈堆父子进程共用,内核将这些文件权限改为只读,若父,子要对某部分数据修改时,内核会给这块区域内存创建一个副本。
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid_t n;
pid=getpid();//得到父进程的pid
int data=10;
printf("the father's pid:%d\n",pid);
n=fork();//创建进程
pid_t fpid=getpid();
if(n>0){//进入父进程
printf("the father's pid:%d,his pid:%d,fanhui:%d\n",pid,fpid,n);
}
if(n == 0){//进入子进程
printf("the child pid:%d,his pid:%d,fanhui:%d\n",pid,fpid,n);
data=data+10;
}
if(fpid == pid){
printf("the data:%d\n",data);
}else{
printf("the data:%d,pid:%d\n",data,getpid());
}
return 0;
}
7.fork创建子进程的目的:
答: (1)父进程收到用户某种指令,调用fork使子进程运行不同的代码实现用户需要的某种功能,如微信聊天服务器收到指令创建独立的子进程(聊天窗口)。
(2)一个进程执行不同程序,这对shell是常见情况。
7.vfork和fork的区别:
答:* vfork直接使用父进程数据不拷贝,因此会改变父进程数据。
* vfork保证子进程先运行,之后调用exit()结束子进程父进程才开始,(要用正确结束方式,否则会破坏父进程中的数据)。
#include <unistd.h>
#include <stdlib.h>
int main()
{
int cnt=0;
int data;
pid_t pid;
printf("piease input a data:\n");
scanf("%d",&data);
if(data == 1){
pid=vfork();//vfork保证子进程先运行,进入子进程
while(1){
printf("do net request %d\n",getpid());//打出子进程的pid
sleep(1);
cnt++;
if(cnt == 3){
exit(-1);//当cnt=3时推出子进程
}
printf("cnt:%d\n",cnt);//打出cnt的值此时已被子进程改变
}
}
return 0;
}
8.子进程退出方式;
答:正常退出:main函数调用return;进程调用exit();进程调用_exit(),_Exit(),(推荐用exit因为他结束时会释放代码所使用的内存而另两个不会)他们会将退出的状态作为参数传给函数,参数就是()中的内容。
* 进程的最后一个线程返回:
最后一个线程调用pthread_exit()函数;
异常退出:调用about;当进程收到某些信号如ctrl+c;
最后一个线程对取消(cancellation)请求做出响应; 此时内核产生一个指示其异常终止原因的终止状态;任何时候该终止进程的父进程都可以调用wait或waitpid函数得到其终止状态。
异常退出可能会破坏父进程中的数据。
9.父进程等待子进程退出:
等待的原因:父进程创建子进程是为了完成某项任务,要想知道子进程完成任务的情况就必须等待子进程结束且exit()返回括号中的状态参数,如果不等待就无法确定完成的状态。
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出。如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
子进程退出有5种正常情况表示任务完成;3种异常退出表示任务未完成然后判断是人为中断还是系统出错。
两个函数:
wait(&status)原型pid_t wait(int *status);
waitpid(&status)原型pid_t waitpid(pid_t pid, int *status, intoptions);
* 参数1:要回收子进程的PID号,参数2:和wait的参数一样,参数三:选择阻塞和非阻塞两种工作模式(0阻塞,WNOHANG 非阻塞)
* 返回值:返回回收的子进程的PID
wait和waitpid的差别:
(1)基本功能是一样的,都是用来回收子进程的
(2)waitpid可以回收指定PID的子进程
(3)waitpid可以阻塞式和非阻塞式两种工作模式
pid_t wait(int *status);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
int status=10;
pid=fork();
if(pid>0){
while(1){
wait(&status);//如果对进程结束的状态不感兴趣括号里可以设为NULL
printf("the status is:%d\n",status);
printf("the chiled status is:%d\n",WEXITSTATUS(status));
*这个宏用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5
*WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
printf("the father's pid:%d\n",getpid());
printf("cnt:%d\n",cnt);
sleep(2);
}
}
if(pid == 0){
while(1){
printf("the chiled's pid:%d;the father's pid is:%d\n",getpid(),getppid());
cnt++;
sleep(2);
if(cnt == 5){
exit(7);
}
}
}
return 0;
}
pid_t waitpid(pid_t pid, int *status, int options);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
int status=10;
pid=fork();
if(pid>0){
while(1){
waitpid(pid,&status,WNOHANG);//以阻塞的方式收集pid进程的结束信息
printf("the status is:%d\n",status);
printf("the chiled status is:%d\n",WEXITSTATUS(status));
printf("the father's pid:%d\n",getpid());
printf("cnt:%d\n",cnt);
sleep(2);
}
}
if(pid == 0){
while(1){
printf("the chiled's pid:%d;the father's pid is:%d\n",getpid(),getppid());
cnt++;
sleep(2);
if(cnt == 5){
exit(7);
}
}
}
return 0;
}
没有被父进程收集状态的子进程结束后会成为僵尸进程。
10.若在子进程运行过程中父进程先关闭,则系统自动使pid=1的init进程成为该子进程的新的父进程。
11.exec函数的使用:
答:一个进程要执行一个不同的程序,这对shell是常见的情况,这种情况下子进程从fork返回后立即调用exec。
当进程调用exec使该进程被新程序完全替代,但不会创造新进程,因此pid不会改变。调用成功不返回,失败是返回-1;
execl: 原型: int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
path:可执行文件的路径名字;arg:可执行文件的所有参数,第一个为可执行文件的名字,没有路径且必须用NULL结尾。
n=fork(); //fork创建子进程;
if(n>0){ //判断返回值进入父进程;
wait(NULL); //wait等待子进程结束并接受返回的状态值;防止子进程变为僵尸进程;
}
if(n == 0){ //判断进入子进程;
execl("/bin/date","date",NULL,NULL);//ececl的用法;
// execlp("date","date",NULL,NULL);//exec只写文件名;lp的用法;会自动查找path环境变量,因此不用写路径只写文件名;
printf("TEXT!!!!");
exit(1); //被执行程序中有return作为结束标志,所以这个可以不用;
execv: 原型: int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
n=fork();
if(n>0){
wait(NULL);
}
if(n == 0){
char *argv[]={"ps","-l",NULL}; //将可执行文件的参数写进一个指针数组;
// if(execv("/bin/ps",argv) == -1){ //用数组名作为参数;并判断是否调用成功;
if(execvp("ps",argv) == -1){ //加上p后可以自动查找环境变量因此不用写路径;
printf(" exec error!!");
perror("why:");
}else{
printf("TEXT!!!");
exit(-1);
}
12.system函数运用:// int system(const char *command);//command为要执行文件的文件名。函数调用成功返回状态值用wait接收,当文件不能执行时返回127,失败返回-1;
int main()
{
printf("this pro get system date:\n");
if(system("ps") == -1){ //判断是否成功;
printf("execl failed!\n");
perror("why:");
}
printf("after system\n"); //system调用不会完全替代原进程,所以调用成功还会执行这一句。
return 0;
}
13.popen函数应用:FILE *popen(const char *command, const char *type); command为执行文件名;type只能是读“r”写“w”的一种若为r则返回值文件标准输出,w为标准输入。比system的优点是可以得到输出结果。
int main()
{
char ret[1024]={'\0'}; //定义一个字符数组接收输出内容;
FILE *fd; //popen返回一个FILE型的数据流;
fd=popen("ps","r"); //调用popen;
if(fd == NULL){ //判断是否成功;
printf("popen error!!");
}else{
int nread=fread(ret,1,1024,fd); //将输出内容读到ret里;
printf("nread=%d,ret:%s\n",nread,ret); 打印;
}
pclose(fd);//关闭文件;
return 0;
}