zoukankan      html  css  js  c++  java
  • LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间

    1什么是进程:进程是一个执行中的程序

    执行的程序: 代码->资源->CPU
    进程有很多数据维护:进程状态/进程属性
    所有进程属性采用的一个树形结构体维护


    ps  -a//所有进程
    ps -aue //有效进程


          进程状态:(man ps)
           D    Uninterruptible sleep (usually IO)
           R    Running or runnable (on run queue)
           S    Interruptible sleep (waiting for an event to complete)
           T    Stopped, either by a job control signal or because it is being traced.
           W    paging (not valid since the 2.6.xx kernel)
           X    dead (should never be seen)
           Z    Defunct ("zombie") process, terminated but not reaped by its parent.
         For BSD formats and when the stat keyword is used, additional characters may be
           displayed:
           <    high-priority (not nice to other users)
           N    low-priority (nice to other users)
           L    has pages locked into memory (for real-time and custom IO)
           s    is a session leader 
           l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 多进程
           +    is in the foreground process group 前台进程


    top 实时查看进程
    pstree 查看进程树
    kill 向进程发送信号   kill -s 信号 进程ID
    kill -l  //查看所有信号
    kill -s 9 224  //关闭224进程

    2 创建进程

    1  进程有关的创建函数

    1.1 system

    int system(const char*filename); 
    建立一个独立的进程,拥有独立的代码空间
    等待新的进程执行完毕,system才返回(阻塞)


    使用system调用函数(观察进程ID 观察阻塞)
    新进程的返回值和system返回值有关系
    1任何进程的返回值不要超过255
    2 system中进程的返回值存放在system返回值的8-15位
    可以通过printf("%d ",r>>8 ); 这样来获得返回值
    3 linux中有专用的获得返回状态的宏
          WEXITSTATUS(status) //#include<wait.h>  #include <sys/wait.h>

    sys.c

    	#include <stdio.h>
    		#include <unistd.h>
    
    
    		int main()
    		{
    			printf("%d
    ",getpid());
    			sleep(2);
    			return 254;
    		}
    
    
    		sys2.c
    		#include <stdio.h>
    		#include <unistd.h>
    		#include <wait.h>
    		#include <sys/types.h>
    		int main()
    		{
    			int r;
    			printf("%d
    ",getpid());
    			r = system("./sleep");
    			//printf("%d
    ",r>>8 );
    			printf("%d
    ",WEXITSTATUS(r));
    			
    		}
    



    gcc sys.c -o sleep
    gcc sys2.c 
    ./a.out

    1.2 popen:创建子进程

    FILE *popen(const char *command, const char *type);

    int pclose(FILE *stream);


    案例:使用popen调用ls -l 并且建立一个管道读取输出

    #include <stdio.h>
    #include <unistd.h>
    
    
    
    
    int main()
    {
    	FILE *fp = popen("cat file","r"); //注意这里是读取输出,不是打开文件 不能直接写文件名
    	//FILE *fp = popen("ls -l","r");
    	if(!fp)
    	{
    		perror("popen");
    		return 1;
    	}
    	
    	int fd = fileno(fp);
    	int r;
    	char buf[1024] = {0};
    	while((r=read(fd,buf,1023)) > 0)
    	{
    		buf[r] = 0;
    		printf("%s",buf);
    	}
    	pclose(fp);
    	
    }
    




    1.3 exec系列函数

           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[]);


    作用: 替换当前代码空间中的数据
    函数本身不创建新的进程
    第一个参数:替换的程序 
    第二个参数: 命令行
    命令行格式:命令名 选项参数 NULL
    命令行结尾必须是空字符串


    execl execlp的区别:
    execl   只在当前路径搜索
    execlp 可以使用系统搜索路径(which能找到)
    如果都找不到,可以使用绝对路径


    命令锁指向的程序  命令本身 参数列表
    如果返回-1  失败

    #include <stdio.h>
    
    
    int main()
    {
    	int r = execl("/bin/ls","ls","-l",0); // 只能调用当前路径
    //	int r = execlp("ls","ls","-l",0);// 能调用系统路径  
    	printf("调用结束%d
    ",r);
    	return 0;
    }
    




    1.4 fork

    pid_t fork()

    1 创建进程
    2 新进程的代码是什么:克隆父进程的挨骂
    而且克隆来执行的位置
    3 在子进程中不调用fork 所有返回值=0
    4 父子进程同时执行



    #include <stdio.h>
    #include <unistd.h>
    int main()
    {
    	printf("创建进程前
    ");
    	int pid = fork(); //父进程执行的
    	while(1)
    {
    	if(pid == 0)
    	{
    		printf("子进程 %d
    ",getpid());
    	}
    	else
    	{
    		printf("父进程 %d
    ",getpid());
    	}	
    }	
    }
    	







    3 应用进程

    使用fork实现多任务(unix本身是不支持线程的)
    1 进程
    2 线程
    3 信号
    4 异步
    5 进程池与线程池

    4 理解进程

    1父子进程的关系
    独立的两个进程
    互为父子关系
    使用pstree看到
         ├─gnome-terminal─┬─bash───a.out───a.out //父子关系
         │                ├─bash───pstree
         │                ├─gnome-pty-helpe
         │                └─2*[{gnome-terminal}]




    2 问题:
    1如果父进程先结束 子进程在么办
    子进程将变成孤儿进程,直接依托根进程(init)
    孤儿 进程是没有危害的


    init─┬─NetworkManager─┬─dhclient
    │                └─{NetworkManager}
    ├─a.out


    2 如果子进程先结束 父进程怎么办
    子进程先结束,子进程会成为僵尸进程。
    僵尸进程的特点: 不占用内存 cpu,但在进程任务管理树上占用一个节点(宝贵)
           实际上僵尸进程会造成进程名额的资源浪费。一定要处理僵尸进程
       ├─gnome-terminal─┬─bash───pstree
      │                ├─bash───a.out───a.out


    3 僵尸进程使用wait回收(阻塞函数)
    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);
    wait 阻塞直到任意进程结束,status来接收进程返回值,返回值表示返回的进程号
    waitpid 阻塞直到指定进程结束

    WEXITSTATUS(status) 解析返回值  status的 8-15位是进程的返回值


    4 父进程怎么知道子进程退出?
    子进程结束时,通常会向父进程发送一个SIGCHLD信号

    5父进程处理子进程的信号
    #include <signal.h>
           typedef void (*sighandler_t)(int);
           sighandler_t signal(int signum, sighandler_t handler);
    signal 的功能 :
    向系统注册,只要接收到信号signal,系统停止进程,执行handler函数,
    当函数执行完毕,继续原来的进程 (软中断)
    5.1实现处理函数
    5.2 使用signal绑定信号与函数


    只有当子进程退出时才用wait,因为wait是一个阻塞函数。所以wait和signal一起用。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include <signal.h>
    void deal(int s)
    {
    	printf("回收中
    ");
    	sleep(5);
    	int status;	
    	wait(&status);
    	printf("回收完毕%d
    ",WEXITSTATUS(status));
    }
    
    
    int main()
    {
    	printf("创建进程前
    ");
    	int pid = fork(); //父进程执行的
    	if(pid == 0)
    	{
    		printf("子进程 %d
    ",getpid());
    		sleep(5);
    		return 88;
    	}
    	else
    	{
    		
    		printf("父进程 %d
    ",getpid());
    		signal(SIGCHLD,deal);
    		while(1)
    		{
    			sleep(1);
    			printf("parent
    ");
    		}
    		return 0;
    	}	
    }
    





    zhao@ubuntu:~/unix/5$ ./a.out 
    创建进程前
    父进程 2324
    子进程 2325
    parent
    parent
    parent
    parent
    回收中
    回收完毕88
    parent
    parent
    parent
    ^C

    6 父子进程的内存空间

    6.1全局变量 局部变量 堆变量 都会被子进程拷贝,但与原先的独立。

    注意 堆内存被复制了,需要分别在各个进程中手动释放。


    子进程克隆了父进程的全局区和局部区内存,但内存区域指向不同的物理空间。
    尽管克隆但内存独立,不能相互访问。
    进程间通信(IPC)是大问题。


    6.2 内存映射与子进程:

    内存映射的属性,决定子进程和父进程是否映射在同一物理空间。
    MAP_SHARED: 映射到同一物理空间。 (改一个进程中的,其他进程的也变化)
    MAP_PRIVATE:映射到不同的物理空间。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    
    
    int main()
    {
    	int *a = mmap(0,4,PROT_READ|PROT_WRITE,
    		      MAP_ANONYMOUS|MAP_SHARED,0,0);
    	*a = 20;
    	int pid = fork(); //父进程执行的
    	if(pid == 0)
    	{
    		*a= 90;
    		printf("parent :a=%d
    ",*a);//90
    	}
    	else
    	{
    		sleep(3);
    		printf("child :a=%d
    ",*a);//90
    	}	
    	
    }
    	

    因为使用的是MAP_SHARED ,所以射到了同一物理空间, 改动会影响其它的.

    若使用MAP_PRIVATE 就可以映射到不同物理空间.

    6.3 文件描述符的拷贝

    每个进程都维护一个文件描述符列表。
    父子进程间,拷贝了文件描述符, 相同文件描述符指向的是同一个文件内核对象。
    1 各个进程的文件描述符都需要close。
    2 对文件的读写会改变文件对象的读写指针位置,对所有进程都有影响。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    
    int main()
    {
    	int fd = open("test.txt",O_RDWR);
    	int pid = fork(); //父进程执行的
    	if(pid == 0)
    	{
    		printf("parent:
    ");
    		char buf[1024] ={0};
    		lseek(fd,0,SEEK_SET);
    		read(fd,buf,1023);
    		printf("%s
    ",buf);
    		close(fd);
    	}
    	else
    	{
    		printf("child:
    ");
    		char buf[1024] ={0};
    		lseek(fd,0,SEEK_SET);
    		read(fd,buf,1023);
    		printf("%s
    ",buf);
    		close(fd);
    	}	
    	
    }
    




    进程的数据交换,基于两种方式:
    内存: 有序/无序:mmap
    文件:有序/无序:普通文件
    基于内核对象:文件/内存/队列

  • 相关阅读:
    Executors 构建线程池
    结构型模式——Bridge(未完成)
    结构型模式——Adapter
    创建型模式——Builder
    创建型模式——Abstract Factory
    Java与线程
    Java内存模型
    类加载
    Class类文件的结构
    垃圾收集器
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3144821.html
Copyright © 2011-2022 走看看