linux进程笔记

一.前言

1.程序和进程

程序

是存在磁盘上的二进制文件

进程

是抽象的,是动态的,是占用系统资源的(内存,cpu); 磁盘不属于系统资源

2.单道程序设计模型和多道程序设计模型

单道程序设计模型

Dos操作系统 任务是一个一个执行的,不能多个任务同时执行

多道程序设计模型

程序是分时进行的,由于cpu速度很快,所以在人类的眼里,所有任务几乎是同时进行的,我们称之为并发

3.cpu


在这里插入图片描述
在这里插入图片描述

4.PCB

在这里插入图片描述
在这里插入图片描述

1.进程ID
2.进程的四种状态:运行,就绪,挂起,停止
3.进程的切换存储的信息
4.进程虚拟地址的映射
5.进程所处的路径
6.进程所处的终端
__________________________________________
7.umask掩码
8.用户ID和组ID
9.会话和进程组
10.信号量
11.文件描述符
查看资源上限
ulimit -a 

5.环境变量

1.特征

1.是字符串
2.名字=值(:值)
3.是以进程为单位的

2.常用的环境变量

PATH SHELL HOME LANG TERM 

3.写一个小程序,打印环境变量

#include <stdio.h>
extern char** environ;

int main()
{
    for(int i = 0; environ[i] != NULL; i++)
    {
        printf("%s\n",environ[i]);
	}
    return 0;
}
//这个环境变量仅代表这个程序运行的环境变量
env export 这种显示的环境变量,是linux终端的环境变量,因为开启一个终端,相当于开启了一个进程

6.关于系统变量的几个函数

NAME  setenv - change or add an environment variable
SYNOPSIS
       #include <stdlib.h>
       int setenv(const char *name, const char *value, int overwrite);
       int unsetenv(const char *name);
If name does exist in the  environment,  then  its  value is changed to value if overwrite is nonzero;
if overwrite is zero, then the value of name  is  not  changed (and setenv() returns a success status).

The unsetenv() function deletes the variable name from the environment.If  name does not exist in the environment, then the function succeeds,and the environment is unchanged.
  
RETURN VALUE
The setenv() function returns zero on success, or  -1  on  error,  with errno set to indicate the cause of the error.
The  unsetenv()  function returns zero on success, or -1 on error, with errno set to indicate the cause of the error.
NAME  getenv, secure_getenv - get an environment variable
SYNOPSIS #include <stdlib.h>
       char *getenv(const char *name);
RETURN VALUE The getenv() function returns a pointer to the value  in  the  environment, or NULL if there is no match.

示例

#include<stdio.h>
#include<stdlib.h>

int main()
{
    char* val;
    const char* name = "ABD";
    val = getenv(name);
    printf("1,%s = %s\n",name,val);
    
    setenv(name,"haha_day_and_night",1);
    val = getenv(name);
    printf("2,%s = %s\n",name,val);
    
    #if 1
    int ret = unsetenv("ABCDEF");//即使这个系统变量不存在也不会报错;ABCDEF=会报错
    printf("ret = %d\n",ret);
    val = getenv(name);
    printf("3,%s = %s\n",name,val);
    #else
    int ret = unsetenv(name);
    printf("ret = %d\n",ret);
    val = getenv(name);
    printf("3,%s = %s\n",name,val);
    #endif
    return 0;
}
//且紧记,这是当前进程的系统变量

二.与进程相关的函数

1.fork

NAME fork - create a child process
SYNOPSIS #include <sys/types.h>
         #include <unistd.h>
         pid_t fork(void);
创建一个子进程: 0:代表返回的是子进程
    		 >0:该返回值是子进程的ID,但该进程是父进程

RETURN VALUE
       On success, the PID of the child process is returned in the parent, and
       0  is returned in the child.  On failure, -1 is returned in the parent,
       no child process is created, and errno is set appropriately.

2.getpid

NAME  getpid, getppid - get process identification
SYNOPSIS  #include <sys/types.h>
          #include <unistd.h>
          pid_t getpid(void);
          pid_t getppid(void);
ERRORS
       These functions are always successful.

示例代码

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    printf("***************\n");
    pid = fork();
    if(pid == -1)
    {
        perror("fork error\n");
	}else if(pid == 0)//这代表是子进程
    {
        printf("我是子进程:pid = %u,ppid = %u",getpid(),getppid());
    }else//这代表是父进程
    {
		printf("我是父进程:pid = %u,ppid = %u",getpid(),getppid());
    }
    printf("################\n");
    return 0;
}

3.创建多个子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    for(int i = 0; i < 5;i++)//其进程都是第一个进程打印出来的
    {
        pid = fork();
        if(pid == -1)
        {
            perror("fork error\n");
            exit(-1);
        }
        else if(pid == 0)
        {
            break;
        }
    }
    
    sleep(i);
    printf("my is %d ,pid = %u\n",getpid());
    return 0;
}
在终端下,shell占用一个进程,在前台,当我们执行可执行程序时,相当于创建出了一个子进程,同时把前台交给这个程序,当这个程序执行完之后,shell会抢占前台,但是尤其创建的子进程也许没有运行完成,故会和shell抢占终端。

4.getuid

NAME  getuid, geteuid - get user identity
SYNOPSI  #include <unistd.h>
         #include <sys/types.h>
		 uid_t getuid(void);
         uid_t geteuid(void);
china@ubuntu:~/test$ sudo apt-get install tree
getuid :得到用户的实际ID china
geteuid:得到用户的有效ID root
NAME getgid, getegid - get group identity
SYNOPSIS  #include <unistd.h>
          #include <sys/types.h>
		  gid_t getgid(void);
      	  gid_t getegid(void);

5.父子进程共享

父子相同处:全局变量,.data,.text,堆,栈,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式
父子不同处:1.进程ID 2.父ID 3.fork返回值 4.进程运行时间 5.闹钟 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗
当然不是!父子进程间遵循读时共享写时复制的原则.这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
重点:共享pcb中的fd,共享pcb中的信号量

6.gdb

set follow_fork_mode child
set follow_fork_mode parent
一定要在fork函数设置之前才有效
b n if i==3 ; 追踪第3个子进程

三.exec函数族

1.execl

NAME  execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS  #include <unistd.h>
       extern char **environ;
       int execl(const char *path, const char *arg, .../* (char  *) NULL */);
       int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
       int execle(const char *path, const char *arg, .../*, (char *) NULL, 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[]);

示例

execlp("ls", "ls", "-a","-l",NULL);
execl("/bin/ls","ls","-a","-l",NULL);
char* argv[] ={"ls","ls","-a","-l",NULL};
execvp("ls",argv);

返回值

RETURN VALUE
       The exec() functions return only if an error has occurred.  The  return
       value is -1, and errno is set to indicate the error.

代码示例

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	pid_t pid;
	pid = fork();
	char c;
	int ret;
	int fd = open("1.txt",O_RDWR|O_CREAT,0777);
	if(fd == -1)
	{
		perror("open 1.txt error\n");
	}
	if(pid < 0)
	{
		perror("fork error!!!\n");
		exit(1);
	}
	else if(pid == 0)
	{
		printf("I'm child\n");
	}
    else
	{
		dup2(fd,1);//将标准输出重定向到文件
		execl("/bin/ls","ls","-a","-l",NULL);
        perror("error");//如果上面函数没有出错,不会到这条语句来
        exit(-1);
	}
	close(fd);
	return 0;
}

l : list	命令行参数列表
v : vector	使用命令行参数数组
e : environment	使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
p : path   搜索path用的变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MhKmWuOK-1648219043810)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210808125721879.png)]

四.回收子进程

1.孤儿进程

孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init,进程,称为init进程领养孤儿进程。
#include<stdio.h>
#include<unistd.h>

int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error\n");
	}
    else if(pid == 0)
    {
        while(1)
        {
            sleep(1);
            printf("child pid = %d, ppid = %d", getpid(),getppid());
        }
    }
    else
    {
        sleep(9);
        printf("father pid = %d, ppid = %d",getpid(),getppid());
    }
    
    return 0;
}

2.僵尸进程

僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意,僵尸进程是不能使用kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。
#include<stdio.h>
#include<unistd.h>

int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error\n");
	}
    else if(pid == 0)
    {
        sleep(9);
        printf("father pid = %d, ppid = %d",getpid(),getppid());
    }
    else
    {
        while(1)
        {
            sleep(1);
            printf("child pid = %d, ppid = %d", getpid(),getppid());
        }
    }
    
    return 0;
}

3.wait

NAME  wait, waitpid, waitid - wait for process to change state
SYNOPSIS #include <sys/types.h>
         #include <sys/wait.h>
		pid_t wait(int *wstatus);
		pid_t waitpid(pid_t pid, int *wstatus, int options);
wait():  on success, returns the process ID of the terminated child; on error, -1 is returned.
waitpid(): on success, returns the process ID of the child whose  state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is  returned.
On error, -1 is returned.
WNOHANG / wait(0)
成功:返回进程号(>0)
失败:-1
如果等于0:则状态没有改变()

 The value of pid can be:
       < -1   meaning  wait  for  any  child process whose process group ID is
              equal to the absolute value of pid.(进程组里的进程)
       -1     meaning wait for any child process.(任意一个子进程)
       0      meaning wait for any child process whose  process  group  ID  is
              equal to that of the calling process.(进程组)
       > 0    meaning  wait  for  the  child  whose process ID is equal to the
              value of pid.(进程号)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s6co1f1a-1648219043811)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210808134414873.png)]

WIFEXITED(wstatus)
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
WEXITSTATUS(wstatus)
WIFSIGNALED(wstatus)
returns true if the child process was terminated by a signal.
WTERMSIG(wstatus)
    returns the number of the signal that caused the  child  process to terminate.  This macro should be employed only if WIFSIGNALED returned true.
        
WIFSTOPPED(wstatus)
    returns  true  if the child process was stopped by delivery of a signal; this is possible only if the call was  done  using  WUNTRACED or when the child is being traced (see ptrace(2)).
        
WSTOPSIG(wstatus)
    returns the number of the signal which caused the child to stop. This macro should be employed only if WIFSTOPPED returned true.

WIFCONTINUED(wstatus)
    (since Linux 2.6.10) returns  true  if  the  child  process  was resumed by delivery of SIGCONT.
作业:父进程fork 3个子进程,三个子进程一个调用ps.命令,一个调用自定义程序1(正常),一个调用自定义程序2(会出段错误)。父进程使用waitpid,对其子进程进行回收。
#include <stdio.h>
#include <unistd.h>
    
int main()
{
    pid_t pid;
    int i;
    //创建3个子线程
    for(i= 0; i < 3; i++)
    {
        pid = fork();
        if(pid == -1)
        {
            perror("fork %d error",i+1);
            exit(-1);
        }else if(pid == 0)//代表这是子进程
        {
            break;
        }
    }
    
    switch(i)
    {
        case 0:
            printf("I'm child,ppid = %d,pid = %d\n",getpid(),getppid());
            execlp("ps","ps","-a","-u","-x",NULL);
            perror("execlp failure!!!\n");
            exit(-1);
            break;
        case 1:
            printf("I'm child,ppid = %d,pid = %d\n",getpid(),getppid());
            int a[10];
            char* b;
            a = b;
            break;
        case 2:
            printf("I'm child,ppid = %d,pid = %d\n",getpid(),getppid());
			int sum = 1+2;
       		break;
        case 3://回收子进程
            for(int j = 3; j != 0;)//代表子进程全部回收完毕
            {
                sleep(5);
                int status;
                int ret = waitpid(-1,&status,WNOHANG)
                if(ret > 0)
                {
                    if(WIFEXITED(status))
                    {
                        printf("pid = %d, status = %d\n",ret,WEXITSTATUS(status));
                    }
                    if(WIFSIGNALED(status))
                    {
						printf("pid = %d, signal = %d\n",ret,WTERMSIG(status));
                    }
                    j--;
                }
            }
    }
    return 0;
}

五.进程间通信(IPC)

1.管道

用于进程间通信的有:
1.管道 pipe
2.信号量
3.内存共享
4.socket
管道的特点
1.有两个fd,一个用于读端,一个用于写端
2.半双工通信
3.只允许1次读
4.只允许共同祖先通信
read:读端
管道中有数据:读
管道中无数据:1.写端关闭,则返回0  2.写端未关闭,则阻塞等待读

write:写端
1.读端关闭,则返回信号量
2.读端未关闭:管道未满,返回写入的字节数,如果已满,则阻塞
NAME  pipe, pipe2 - create pipe
SYNOPSIS #include <unistd.h>
         int pipe(int pipefd[2]);
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and  errno  is
       set appropriately.

示例程序:父子间通信

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main()
{
    //1.创建管道
    int pipefd[2];
    int ret = pipe(pipfd);
    if(ret == -1)
    {
        perror("pipe error !!!\n");
	}
    
    //2.创建子进程
    pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        perror("fork error!!!\n");
	}
    else if(pid == 0)//读
    {
        char str[1024];
        close(pipefd[1]);
        ret = read(pipefd[0],str,1024);
        if(ret == 0)
            exit(1);
        write(STDOUT_FILENO,str,ret);
        close(pipefd[0]);
    }
    else//写
    {
        sleep(1);
        close(pipefd[0]);
        char *str = "hello,caojiayi!!!\n";
        write(pipefd[1],str,strlen(str)+1);
        close(pipefd[1]);
    }
    return 0;
}

示例程序2 ls | wc -l

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    //1.创建管道
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
        perror("pipe error!!\n");
    }
    //2.创建子进程
    pid_t pid;
    pid = fork();
    //3.父读子写
    if(pid == -1)
    {
        perror("fork error!!!\n");
    }
    else if(pid > 0)//父亲读
    {
        close(fd[1]);
        dup2(fd[0],STDIN_FILENO);//重定向读文件
        execlp("wc","wc","-l",NULL);//成功没有返回值,也不会执行下面的程序
		perror("execlp error");//失败执行
    }
    else//孩子写
    {
        close(fd[0]);
        dup2(fd[1],STDOUT_FILENO);//重定向写文件
        execlp("ls","ls",NULL);//成功没有返回值,也不会执行下面的程序
        perror("execlp error");//失败执行
    }
    
    return 0;
}
//程序的进程不能调换,因为read会一直等到读;若是子进程读,那么容易形成孤儿进程

示例程序2 ls | wc -l 父子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    //1.创建管道
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
        perror("pipe error!!\n");
    }
    //2.创建子进程
    pid_t pid;
    int i;
    for(int i = 0; i < 2;i++)
    {
        pid = fork();
        if(pid == -1)
    	{
        	perror("fork error!!!\n");
    	}else if(pid == 0)
        {
            break;
        }
	}
    
    if(i == 0)//兄弟读
    {
        close(fd[1]);
        dup2(fd[0],STDIN_FILENO);//重定向读文件
        execlp("wc","wc","-l",NULL);//成功没有返回值,也不会执行下面的程序
		perror("execlp error");//失败执行
    }
    else if(i == 1)//弟弟写
    {
        close(fd[0]);
        dup2(fd[1],STDOUT_FILENO);//重定向写文件
        execlp("ls","ls",NULL);//成功没有返回值,也不会执行下面的程序
        perror("execlp error");//失败执行
    }else//父亲回收
    {
  		close(fd[0]);
        close(fd[1]);//一定要关闭父亲的读管道,否则无法形成单向闭环在屏幕中输出
        wait(NULL);
        wait(NULL);
    }
    
    return 0;
}

2.共享内存

1.mmap和munmap

NAME
       mmap, munmap - map or unmap files or devices into memory

SYNOPSIS
       #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
		addr: 通用指针类型,为什么是通用指针类型,因为你不知道你映射进去的内存是什么类型的
        length : 内存区的大小
         port: 对这块内存的操作权限 PORT_READ,PORT_WRITE,PORT_READ | PORT_WRITE
         flags: 内存与文件之间的关系 MAP_SHARED,MAP_PRIVATE MAP_ANON
         fd:要映射的文件描述符
         offset:相较于文件的偏移量
       int munmap(void *addr, size_t length);
		addr: 映射区域的首地址
        length: 映射区域的大小

注意事项

1.mmap映射的权限要 <= 文件的权限 否则会报权限不足的错误
2.mmap映射过程中内含读的的一次操作,故mmap一定要有读权限
3.off_t offset 的偏移量是4k 必须是4k的整数倍
4.mmap映射的空间要 <= length

例程:文件映射到内存区

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    //1.先创建一个文件
    int fd = open("1.txt",O_CREAT | O_RDWR,0664);
    if(fd == -1)
    {
        perror("文件创建失败");
        return -1;
    }
    
    //2.清空文件
    int r = ftruncate(fd,4);
    if(r == -1)
    {
        perror("文件清空失败");
        return -1;
    }
    
    //3.将文件映射到内存
    char *p = mmap(NULL,4,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED)
    {
        perror("mmap error!");
        return -1;
	}
    
    //4.将内容写入内存
    strcpy(p,"a\n");
    
    //5.关闭文件和解映射
    close(fd);
    munmap(p,4);
    
    return 0;
}

匿名内存

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    //1.先创建一个文件
    int fd = open("/dev/zero",O_RDWR);
    if(fd == -1)
    {
        perror("文件打开失败");
        return -1;
    }
    
    //3.将文件映射到内存
    char *p = mmap(NULL,40,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED)
    {
        perror("mmap error!");
        return -1;
	}
    
    //4.将内容写入内存
    strcpy(p,"a");
    printf("%d\n",*p);
    //5.关闭文件和解映射
    close(fd);
    munmap(p,40);
    
    return 0;
}

查看代码追踪

strace ./a.out
判断哪里出现段错误 -g(编译时)
运行时 gdb a.out
run 停下的地方就是出现段错误的地方

3.信号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tzLjnUbn-1648219043813)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210902153224098.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值