Linux C语言学习 IO进程 :day3

【1】库
    1.定义:本质上来说库是一种可执行代码的二进制形式;
    通俗讲就是把一些常用函数的目标文件打包在一起,提供相应
    函数的接口,便于程序员使用;它可以被操作系统载入内存执行。
    由于windows和linux的本质不同,因此二者库的二进制是不兼容的
    2.库的分类:
      1)静态库:静态库在程序编译时会被连接到目标代码中,
    程序运行时将不再需要该静态库,因此体积较大。
      2)动态库在程序编译时并不会被连接到目标代码中,
      而是在程序运行时才被载入,因此在程序运行时还需要动态库
      存在,因此代码体积较小
    3.静态库的制作步骤:
        1-将源文件编译生成目标文件
            gcc -c  add.c -o add.o
        2-创建静态库用ar命令,它将很多.o转换成.a
            ar  crs  libmyadd.a  add.o 
            静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
        3-使用静态链接库
            gcc  main.c  -L.  -lmyadd 
        4-执行./a.out
      优缺点:
        优点:    程序中已f包含代码,运行时不再需要静态库。
                运行时无需加载库,运行速度更快。
        缺点:    静态库中的代码复制到了程序中,使程序会占更多的磁盘和内存空间
                静态库升级后,程序需要重新编译链接
    4.动态库的制作步骤:    
        1-我们用gcc来创建共享库
            gcc -fPIC  -c hello.c  -o hello.o
                -fPIC 创建与地址无关的编译程序
            gcc -shared -o libmyhello.so hello.o
        2-编译代码    
            gcc main.c  -L. -lmyhello
            ldd a.out 查看a.out的运行依赖那些库。
        3-为了让执行程序顺利找到动态库,有三种方法 :
            (1)把库拷贝到/usr/lib和/lib目录下。 
            (2)在LD_LIBRARY_PATH环境变量中加上库所在路径。 
                export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. (最好是绝对路径,要在一行)
                (终端关闭,环境变量就没在了)
                本次配置只在当前终端有效,关闭和关机后就没效了。
                想要永久有效需要到 .bashrc文件中进行修改。
            (3) 添加/etc/ld.so.conf.d/*.conf文件,
            把库所在的路径加到文件末尾,并执行ldconfig刷新
                sudo vi xx.conf
                添加动态库存在的路径,如:
                /home/linux/19072/IO/day3/
     优缺点:    
        优点:    程序在执行时加载动态库,代码体积小
                将一些程序升级变得简单。
                不同的应用程序如果调用相同的库,那么在内存
            里只需要有一份该共享库的实例。
        缺点:运行时还需要动态库的存在,移植性较差    
        
【2】进程:
    1.进程和程序的区别
    程序:编译好的可执行文件
          存放在磁盘上的指令和数据的有序集合(文件)
          程序是静态的,没有任何执行的概念

    进程:一个独立的可调度的任务
          执行一个程序所分配的资源的总称
          进程是程序的一次执行过程
          进程是动态的,包括创建、调度、执行和消亡
    
    2.进程包含三个段:
        1)“数据段”存放的是全局变量、常数以及动态数据分配的数据空间
    (如malloc函数取得的空间)等。
        2)“正文段”存放的是程序中的代码
        3)“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量 
    
    3.进程的类型:
        1)交互进程:交互进程既可以在前台运行,也可以在后台运行。
    该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,
    该类进程会立刻响应。
        2)批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行
        3)守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
    
    4.进程的运行状态:
        1)运行态:此时进程或者正在运行,或者准备运行。
        2)等待态:此时进程在等待一个事件的发生或某种系统资源。
        可中断:处在这种状态下的进程可以被信号中断,接收到信号或被显示地唤醒呼叫,唤醒之后,进程将转变为运行态。
        不可中断:它不会处理信号,只有在它所等待的事件发生时,进程才被显示的唤醒
        3)停止态:此时进程被中止。
        4)死亡态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
       D    uninterruptible sleep (usually IO)(不可中断的等待态)
       R    running or runnable (on run queue)(运行态)
       S    interruptible sleep (可中断的等待态)
       T    stopped, either by a job control signal or because it is
            being traced.(停止态)
       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)(低优先级)
       s    is a session leader(会话组组长)
       l    is multi-threaded (线程)
       +    is in the foreground process group.(前台)
            空:表示后台
            
【3】相关命令:
    1.ps aux:
    2.top:
        shift+‘>’:向下查找
        shift+‘<’:向上查找
        q:退出
    3.nice:按用户指定的优先级运行进程
        PR=NI+20  NI [-20,19]数字越小优先级越高
        nice -n 5 ./a.out
    4.renice:改变正在运行进程的优先级
            renice 5 PID 
    5.kill:发送信号(包括后台进程)
            kill  -num  PID   给指定的进程,发送一个信号。
            kill -l  查看进程信号
                2) SIGINT         停止信号,默认杀死进程。ctrl + c
                3) SIGQUIT        退出信号,默认也是杀死进程。 ctrl + \
                9) SIGKILL         杀死进程,不能被忽略,不能被捕捉
                14) SIGALRM       闹钟信号,默认也是杀死进程
                17)SIGCHLD      儿子状态改变,内核会给它的父亲发送此信号
                18) SIGCONT       唤醒信号,唤醒之后变为后台运行
                19) SIGSTOP       暂停信号, 不能忽略,不能被捕捉
                20) SIGTSTP        暂停信号,  ctrl + z
            补充:killall 进程名 :结束进程
    6.bg  将挂起的进程在后台执行
        bg 编号
    7.fg  把后台运行的进程放到前台运行
        fg 编号
    8.jobs 查看所有的后台进程 :        
    
【4】创建进程函数:
    pid_t fork(void);
    返回值:
        失败:-1 
        成功:
            0 :在子进程中
            >0: 在父进程中返回的是子进程的进程号
    特点:
    1)fork函数是用来创建进程的,fork之后产生了两个进程
    ,每个进程都会有返回值,所以父进程中返回的是子进程
    的进程号(>0);在子进程中返回0
    2)子进程几乎拷贝了父进程的全部内容。包括代码、
    数据、系统数据段中的pc值、栈中的数据、父进程中打开
    的文件等;但它们的PID、PPID是不同的。
    3)父子进程有独立的地址空间,互不影响;当在相应的
    进程中改变全局变量、静态变量,都互不影响。
    4)若父进程先结束,子进程成为孤儿进程,被init进程
    收养,子进程变成后台进程。
    5)若子进程先结束,父进程如果没有及时回收,子进程
    变成僵尸进程(要避免僵尸进程产生)
    
    注意:子进程复制了父进程的全部,虚拟地址空间,fork之前的代码复制
      之后的代码复制并执行。

【5】获取进程号
    pid_t getpid(void);
    功能:获取当前进程的进程号
    pid_t getppid(void);
    功能:获取当前进程的父进程号
    
    这两个函数,只要进程产生就绝对会调用成功。
补充:
   一个进程最多能打开1024个文件描述符。
   父进程和子进程可以通过同一个文件描述符对同一个文件进行操作。
   (将文件描述符复制一份,操作的是同一个文件指针,同一个文件)
    
【6】进程退出函数
    void exit(int status);
    功能:结束进程,刷新缓存
    void _exit(int status);
    功能:结束进程,不刷新缓存
    status是一个整型的参数,可以利用这个参数传递进程
    结束时的状态。
    通常0表示正常结束;
    其他的数值表示出现了错误,进程非正常结束。
    在实际编程时,可以用wait系统调用接收子进程的返回值
    ,进行相应的处理。

【7】进程回收函数
    pid_t wait(int *status);
    功能:阻塞等待子进程退出,回收资源(回收僵尸进程)
    参数:status是一个整型指针,指向的对象用来保存
    子进程退出时的状态。
?      status若为空,表示忽略子进程退出时的状态
?      status若不为空,表示保存子进程退出时的状态
        另外,子进程的结束状态可由Linux中一些特定的宏来测定。
    返回值:成功:子进程的进程号
            失败:-1
            
   WIFEXITED(*status)  判断子进程是否正常结束//wifexited
    //正常结束返回1,非正常结束返回0。

    WEXITSTATUS(*status)  获取子进程返回值//wexitstatus
    //拿到的是一个8位的数,占一个字节,
    //如:exit(10)为10,exit(-10),打印的结果为246
    //若return的值,只是高八位表示。
    
提示:
    调用该函数使进程阻塞(谁调用谁阻塞等待),直到任一个子进程结束或者
    是该进程接收到了一个信号为止。如果该进程没有子进程
    或者其子进程已经结束,wait函数会立即返回。

    pid_t waitpid(pid_t pid, int *status, int options);
    功能:阻塞等待子进程退出,回收资源
    参数:pid:>0 指定子进程进程号
               =-1任意子进程
               =0 等待其组ID等于调用进程的组ID的任一子进程
               <-1等待其组ID等于pid的绝对值的任一子进程
          status:同上
          options:WNOHANG:不阻塞
                    0:阻塞
    返回值:正常:结束的子进程的进程号
              使用选项WNOHANG且没有子进程结束时:0
            出错:-1
    waitpid(-1,NULL,0) ==> wait(NULL);
    
【5】exec函数族:
   1.概念:
    exec函数族提供了一种在进程中启动另一个程序执行的方法。
    它可以根据指定的文件名或目录名找到可执行文件,并用它
    来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,
    原调用进程的内容除了进程号外,其他全部都被替换了。
    
   2.何时使用exec?
     1)当进程认为自己不能再为系统和用户做出任何贡献了时就可以
       调用exec函数,让自己执行新的程序
     2)如果某个进程想同时执行另一个程序,它就可以调用fork函数
     创建子进程,然后在子进程中调用任何一个exec函数。
     这样看起来就好像通过执行应用程序而产生了一个新进程一样

   3.相关函数:
   int execl(const char *path, const char *arg, ...);
   int execv(const char *path, char *const argv[]);
   int execle(const char *path, const char *arg, ..., char *const envp[]);
   int execve(const char *path, char *const argv[], char *const envp[]);
   int execlp(const char *file, const char *arg, ...);
   int execvp(const char *file, char *const argv[]);
         l : list列表,参数要以列表形式,展现出来。
        p : path路径,从系统环境变量中去找可执行文件
        v : 向量数组,将列表的参数装到数组中去。
        e :  函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境。envp也是一个以NULL结尾的字符串数组指针
   4.使用区别:
    1)可执行文件查找方式
        无p:指定完整的文件目录路径,
        有p:只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。
    2)参数表传递方式
        有l:表示逐个列举的方式;
        有v:表示将所有参数构造成指针数组传递,其语法为char *const argv[]
    3)环境变量的使用
        exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。
    这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量,一般为NULL即可    

      1) #include <stdio.h>
         #include <unistd.h>
         int main(int argc, const char *argv[])
         {    
            char * envbuf[100]={"usr=lisi","PWD=/home/sky","PATH=/bin:/usr/bin:/sbin/:",NULL};
            int ret = execle("./a.out", "a.out", "hello","world",NULL, envbuf);
            //char *arg[]={"a.out","asdf","asdf",NULL};
           //int ret = execve("./a.out",arg,envbuf);
            if(ret == -1) {
                  perror("execle");
               return -1;
            }
            return 0;
         }    
    
    2)   #include <stdio.h>
          #include <unistd.h>
          int main(int argc, const char *argv[])
          {
                execlp("printenv", "printenv", NULL);
                //char *buf[2] = {"printenv", NULL};
                //execvp("printenv", buf);
                return 0;
          }
          
        #include <stdio.h>
        #include <unistd.h>
        int main(int argc, const char *argv[])
        {
            char *args[] = {"ls", "-l", "-a", "-h", NULL};
            int ret = execvp("ls", args);
            if(ret == -1) {
                perror("execlp");
                return -1;
            }
            return 0;
        }    
         

【6】守护进程
  //好的守护进程无法杀死,会自动重启
  守护进程: daemon
    什么是守护进程:
    在linux中与用户交互的界面叫终端,从终端
    运行起来的程序都依附于这个终端,当终端关
    关闭时,相应的进程都会被关闭。
    守护进程可以突破这个限制

   守护进程的创建步骤:
    1. 创建子进程,父进程退出 
        fork
    2. 在子进程中创建新会话 //脱离终端
        setsid
    3. 改变当前目录为根目录 
        chdir
    4. 重设文件权限掩码 
        umask
    5. 关闭文件描述符     
        close
作业:
1. 复习今天所学内容
2. 通过父子进程完成文件io对文件的拷贝cp,父进程从文件开始
到文件的一半开始拷贝,子进程从文件的一半到文件末尾。
    fd_src = open源文件
    fd_dest = open目的文件
    size = lseek();
    size=size/2;
    pid = fork();
    if(pid == 0)
    {
        lseek(fd_src, size);
        lseek(fd_dest, size);
        while(1)
        {
            read(fd_src);
            write(fd_dest);
        }
    }else{
        lseek(fd_src, size);
        lseek(fd_dest, size);
        while(1)
        {
            read(,,5);
            write();
        }
    }
    close();
    
    //6
    

        

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值