zoukankan      html  css  js  c++  java
  • Linux 进程(一):环境及其控制

    进程环境

    main启动

    当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序将此启动例程指定为程序的起始地址,接着启动例程从内核中取出命令行参数和环境变量值,然后执行main函数。

    进程终止

    使进程终止的方式有8种,其中5种为正常终止,3种为异常终止:

    终止类型

    说明

    正常终止

    (1)   从main返回

    (2)   调用exit

    (3)   调用_exit或_Exit

    (4)   最后一个线程从启动例程返回

    (5)   在最后一个线程中调用pthread_exit

    异常终止

    (1)   调用abort

    (2)   接收到一个信号

    (3)   最后一个线程对取消请求做出响应

    相关函数:

    #include <stdlib.h>

    void exit(int status);

    void _Exit(int status);

    #include <unistd.h>

    void _exit(int status);

    说明:

    在main中,return(0)与exit(0)是等价的。

    exit函数总是执行一个标准I/O库的清理关闭工作,即对所有打开的流调用fclose函数。

    #include <stdlib.h>

    int atexit(void (*func)(void));

    返回值:成功,0;失败,-1

    说明:

    atexit函数用于向进程注册函数,最多可以注册32个。这些函数将由exit调用。我们将这些函数称为终止处理函数。如果同一个函数登记了多次,则也会被调用多次。

    exit调用顺序与这些函数的注册顺序刚好相反

    # atexit函数的使用
    [root@benxintuzi IO]# cat atexit.c
    #include <stdio.h>
    #include <stdlib.h>
    
    static void my_exit1(void);
    static void my_exit2(void);
    
    int main(void)
    {
            if(atexit(my_exit1) != 0)
                    printf("can't register my_exit1
    ");
            if(atexit(my_exit1) != 0)
                    printf("can't register my_exit1
    ");
            if(atexit(my_exit2) != 0)
                    printf("can't register my_exit2
    ");
    
            printf("main is done
    ");
            return 0;
    }
    
    static void my_exit1(void)
    {
            printf("first exit handler
    ");
    }
    
    static void my_exit2(void)
    {
            printf("second exit handler
    ");
    }
    
    [root@benxintuzi IO]# ./atexit
    main is done
    second exit handler
    first exit handler
    first exit handler

    环境变量

    每个程序都有一张环境表,是一个字符指针数组,每个指针包含一个以null结束的字符串的地址。全局变量extern char** environ指向该环境表的地址:

     

    如下函数用于操作环境变量的值:

    #include <stdlib.h>

    char* getenv(const char* name);

    返回值:成功,返回与name关联的value;失败,返回NULL

    int setenv(const char* name, const char* value, int rewrite);

    int unsetenv(const char* name);

    int putenv(char* str);

    返回值:成功,0;失败,-1

    说明:

    putenv将name=value的字符串放到环境表中。如果name已经存在,则先删除其原来的定义。

    关于参数rewrite:如果rewrite为0,则保留原有的name定义;rewrite非0,则覆盖原有的name定义。

    unsetenv用于删除指定的name定义。

    通常用getenv和putenv来访问特定的环境变量,而非使用environ变量,但是如果要查看整个环境,则仍然需要使用environ,如下: 

    #include <stdio.h>
    
    int main(void)
    {
            extern char** environ;
    
            while(*environ != NULL)
            {
                    printf("%s
    ", *environ);
                    environ++;
            }
    
            return 0;
    }
    
    [root@benxintuzi IO]# ./environ
    HOSTNAME=benxintuzi
    SELINUX_ROLE_REQUESTED=
    SHELL=/bin/bash
    TERM=vt100
    HISTSIZE=1000
    HADOOP_HOME=/bigdata/hadoop-2.6.0
    SSH_CLIENT=192.168.8.1 55561 22
    SELINUX_USE_CURRENT_RANGE=
    QTDIR=/usr/lib/qt-3.3
    QTINC=/usr/lib/qt-3.3/include
    SSH_TTY=/dev/pts/1
    USER=benxintuzi
    ...

    程序的存储布局

    C程序由以下几部分组成:

    正文段:存放代码的地方,通常是可共享的,即该部分内容是可再入的,因此一般只需一份副本,并且设置为只读。

    初始化数据段:包含程序中需要明确指定初值的变量,比如:int maxcount = 99;

    未初始化数据段(bss段):该段的数据不指定初值也可,因为内核在程序开始执行前,会将此段中的数据初始化为0或者NULL。该部分有时也被称为常量区或全局区。

    栈区:存放临时变量,函数地址或者环境上下文信息等。

    堆区:动态存储分配,位于bss段和栈区之间。

    一个典型的存储空间示意图如下:

     

    Linux中的size程序用户报告一个程序所占用的存储空间信息:

    [root@benxintuzi IO]# size passwd01 memstr

    text    data     bss     dec     hex   filename

    1360    264      8       1632    660    passwd01

    2157    284      8       2449    991    memstr

    动态存储分配函数如下:

    #include <stdlib.h>

    void* malloc(size_t size);

    void* calloc(size_t nobj, size_t size);

    void* realloc(void* ptr, size_t newsize);

    void free(void* ptr);

    说明:

    malloc分配指定字节数的存储区,初始值不确定。

    calloc分配指定长度的存储区,初始值为0。

    realloc增加或者减少存储区的长度,新增的区域内初始值不确定。

    资源限制

    每个进程都有一组资源限制,可以使用如下函数查询和更改:

    #include <sys/resource.h>

    int getrlimit(int resource, struct rlimit* rlptr);

    int setrlimit(int resource, const struct rlimit* rlptr);

    返回值:成功,0;失败,-1

    struct rlimit

    {

    rlim_t rlim_cur;  /* soft limit: current limit */

    rlim_t rlim_max;  /* hard limit: maximum value for rlim_cur */

    }

    说明:

    任何一个进程都可将一个软限制值更改为小于等于其硬限制值。

    任何一个进程都可降低其硬限制值,但它不能小于软限制值,而且这种降低对于普通用户是不可逆的。

    只有超级用户进程可以提高硬限制值。

    resource参数取值如下:

    RLIMIT_AS:进程总的可用存储空间的最大长度。

    RLIMIT_CORE:core文件的最大字节。若为0,则阻止创建core文件。

    RLIMIT_CPU:CPU时间的最大量值(秒)。若超过此软限制,则向该进程发送SIGXCPU信号。

    RLIMIT_DATA:数据段总的最大字节长度,包含:初始化数据段、bss段、堆区。

    RLIMIT_FSIZE:可以创建文件的最大字节长度。若超过此软限制,则向该进程发送SIGXFSZ信号。

    RLIMIT_MEMLOCK:进程使用mlock能够锁定的在存储空间中的最大字节长度。

    RLIMIT_MSGQUEUE:进程为消息队列可分配的最大存储字节数。

    RLIMIT_NICE:nice值影响进程的调度优先级。

    RLIMIT_NOFILE:进程可打开的最大文件数量。更改此限制将影响sysconf函数在参数_SC_OPEN_MAX中的返回值。

    RLIMIT_NPROC:每个用户ID可拥有的最大子进程数。更改此限制将影响sysconf函数在参数_SC_CHILD_MAX中的返回值。

    RLIMIT_RSS:最大驻留内存集字节长度。

    RLIMIT_SIGPENDING:进程可排队的信号最大数量。

    RLIMIT_STACK:栈的最大字节长度。

    RLIMIT_SWAP:用户可消耗的交换空间的最大字节数。

    如下程序打印当前系统支持的所有资源限制情况:

    [root@benxintuzi IO]# cat reslimit.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/resource.h>
    
    #define doit(name)      pr_limits(#name, name)
    
    static void     pr_limits(char*, int);
    
    int main(void)
    {
    #ifdef  RLIMIT_AS
            doit(RLIMIT_AS);
    #endif
    
            doit(RLIMIT_CORE);
            doit(RLIMIT_CPU);
            doit(RLIMIT_DATA);
            doit(RLIMIT_FSIZE);
    
    #ifdef  RLIMIT_MEMLOCK
            doit(RLIMIT_MEMLOCK);
    #endif
    
    #ifdef RLIMIT_MSGQUEUE
            doit(RLIMIT_MSGQUEUE);
    #endif
    
    #ifdef RLIMIT_NICE
            doit(RLIMIT_NICE);
    #endif
    
            doit(RLIMIT_NOFILE);
    
    #ifdef  RLIMIT_NPROC
            doit(RLIMIT_NPROC);
    #endif
    
    #ifdef RLIMIT_NPTS
            doit(RLIMIT_NPTS);
    #endif
    
    #ifdef  RLIMIT_RSS
            doit(RLIMIT_RSS);
    #endif
    
    #ifdef  RLIMIT_SBSIZE
            doit(RLIMIT_SBSIZE);
    #endif
    
    #ifdef RLIMIT_SIGPENDING
            doit(RLIMIT_SIGPENDING);
    #endif
    
            doit(RLIMIT_STACK);
    
    #ifdef RLIMIT_SWAP
            doit(RLIMIT_SWAP);
    #endif
    
    #ifdef  RLIMIT_VMEM
            doit(RLIMIT_VMEM);
    #endif
    
            exit(0);
    }
    
    static void pr_limits(char* name, int resource)
    {
            struct rlimit           limit;
            unsigned long long      lim;
    
            if (getrlimit(resource, &limit) < 0)
                    printf("getrlimit error for %s
    ", name);
            printf("%-14s  ", name);
            if (limit.rlim_cur == RLIM_INFINITY) {    # 常量RLIM_INFINITY表示无限制
                    printf("(infinite)  ");
            } else {
                    lim = limit.rlim_cur;
                    printf("%10lld  ", lim);
            }
            if (limit.rlim_max == RLIM_INFINITY) {
                    printf("(infinite)");
            } else {
                    lim = limit.rlim_max;
                    printf("%10lld", lim);
            }
            putchar((int)'
    ');
    }
    
    [root@benxintuzi IO]# ./reslimit
    RLIMIT_AS       (infinite)  (infinite)
    RLIMIT_CORE              0  (infinite)
    RLIMIT_CPU      (infinite)  (infinite)
    RLIMIT_DATA     (infinite)  (infinite)
    RLIMIT_FSIZE    (infinite)  (infinite)
    RLIMIT_MEMLOCK       65536       65536
    RLIMIT_MSGQUEUE      819200      819200
    RLIMIT_NICE              0           0
    RLIMIT_NOFILE         1024        4096
    RLIMIT_NPROC          3861        3861
    RLIMIT_RSS      (infinite)  (infinite)
    RLIMIT_SIGPENDING        3861        3861
    RLIMIT_STACK      10485760  (infinite)

    进程控制

    进程标识

    每个进程都用一个非负整型ID唯一标识,但是该进程ID是可复用的,只不过大多数Unix系统采用延迟复用算法,即如果一个进程终止后,必须经过某个固定的间隔才可将该进程ID用于表示其他进程,这样做可以防止将新进程误认为是前一个已经终止的进程。

    特殊进程:

    进程ID

    说明

    0

    用于标识调用进程(交换进程swapper),其并不执行任何磁盘上的程序,因此也被称为系统进程。

    1

    用于标识init进程,在自举结束时由内核调用,位于/sbin/init,用于在自举后启动一个Unix系统。init通常读取与系统有关的初始化文件(/etc/rc*或/etc/inittab、/etc/init.d),将系统引导到一个状态。init进程不会终止,但是其并不是系统进程,而是一个普通的用户进程,只不过是以超级用户特权运行。

    其他ID标识操作函数:

    #include <unistd.h>

    pid_t getpid(void);

    pid_t getppid(void);

    uid_t getuid(void);

    uid_t geteuid(void);

    gid_t getgid(void);

    gid_t getegid(void);

    说明:

    getpid返回进程ID,getppid返回父进程ID,getuid返回实际用户ID,geteuid返回有效用户ID,getgid返回实际组ID,getegid返回有效组ID。

    进程创建

    函数

    说明

    #include <unistd.h>

    pid_t fork(void);

    (1)   fork函数用于子进程的创建,调用一次,返回两次。父进程返回子进程ID,而子进程返回0。

    (2)   子进程是父进程的副本,获取父进程的数据、堆、栈等的副本(不共享)。父子进程共享正文段。

    (3)   由于在fork之后经常伴随着exec,所有现在很多fork实现并不执行数据、堆和栈的完全副本,而是采用写时复制(Copy-On-Write, COW)技术,即这些区域先由父子进程共享,内核将他们的权限设为只读,如果其中任何一个需要修改时,内核只为需要修改的区域部分制作一个副本。

    (4)   在fork之后先执行父进程还是子进程是不确定的,取决于内核使用的调度算法。

    [root@benxintuzi process]# cat fork01.c
    #include <unistd.h>
    #include <stdio.h>
    
    int    globvar = 6;            /* external variable in initialized data */
    char    buf[] = "a write to stdout
    ";
    
    int main(void)
    {
            int             var;            /* automatic variable on the stack */
            pid_t   pid;
    
            var = 88;
            if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                    printf("write error
    ");
    
            printf("before fork
    ");        /* we don't flush stdout */
    
            if ((pid = fork()) < 0) 
            {
                    printf("fork error
    ");
            } 
            else if (pid == 0) 
            {                                               /* child */
                    globvar++;                              /* modify variables */
                    var++;
            } 
            else 
            {
                    sleep(2);                               /* parent */
            }
    
            printf("pid = %ld, glob = %d, var = %d
    ", (long)getpid(), globvar, var);
    
            return 0;
    }
    
    [root@benxintuzi process]# ./fork01
    a write to stdout
    before fork
    pid = 2270, glob = 7, var = 89
    pid = 2269, glob = 6, var = 88

    fork失败的两个主要原因:

    (1)   系统中进程数量太多了。

    (2)   该实际用户ID所拥有的进程数超过了系统限制(由CHILD_MAX指定)。

    vfork

    vfork函数用于创建一个新进程来执行一个新程序。vfork保证子进程先运行,在其调用exec或exit之后父进程才可能被调用执行,所以如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。

    将上述程序用vfork重写,由于vfork可以保证在调用exec或者exit之前,父进程不会执行,因此不用在父进程中调用sleep休眠了:

    [root@benxintuzi process]# cat vfork.c
    #include <unistd.h>
    #include <stdio.h>
    
    int             globvar = 6;            /* external variable in initialized data */
    char    buf[] = "a write to stdout
    ";
    
    int main(void)
    {
            int             var;            /* automatic variable on the stack */
            pid_t   pid;
    
            var = 88;
            if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                    printf("write error
    ");
    
            printf("before vfork
    ");       /* we don't flush stdout */
    
            if ((pid = vfork()) < 0) 
            {
                    printf("fork error
    ");
            } 
            else if (pid == 0) 
            {                                               /* child */
                    globvar++;                              /* modify variables */
                    var++;
                    exit(0);                                /* child terminates */
            } 
            /* parent continues here */
            printf("pid = %ld, glob = %d, var = %d
    ", (long)getpid(), globvar, var);
    
            return 0;
    }
    
    [root@benxintuzi process]# gcc vf./vfork
    a write to stdout
    before vfork
    pid = 2295, glob = 7, var = 89

    进程终止

    进程有5种正常终止方式以及3种异常终止方式:

    5种正常终止方式:

    (1)   在main函数内执行return语句,等效于调用exit。

    (2)   调用exit函数。exit操作包括调用各种终止处理函数(exit handler)。

    (3)   调用_exit或_Exit函数。_Exit的存在是为进程提供一种无需运行终止处理函数或者信号处理函数而终止的方式。_exit函数是由exit函数调用的,用于处理Unix系统特定的细节。

    (4)   进程的最后一个线程在其启动例程中执行return语句。但是该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

    (5)   进程的最后一个线程调用pthread_exit函数。

    3种异常终止方式:

    (1)   调用abort,其产生SIGABRT信号。

    (2)   进程接收到某些信号时。

    (3)   最后一个线程对“取消”请求做出响应。

    不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开的文件描述符,释放其所占用的存储器等。

    获取进程终止状态

    当一个进程正常或异常终止时,内核就会向其父进程发送SIGCHLD信号,进而父进程调用wait或waitpid获取其子进程的终止状态。调用wait时可能有如下情况发生:

    (1)   如果当前进程没有任何子进程,则立即出错返回。

    (2)   如果所有子进程都在运行,则阻塞。

    (3)   如果其中一个子进程已经终止,则取得该子进程的终止状态后立即返回。

    #include <sys/wait.h>

    pid_t wait(int* statloc);

    pid_t waitpid(pid_t pid, int* statloc, int options);

    返回值:成功,返回进程ID;失败,返回0或-1

    说明:

    进程的终止状态存放于statloc指向的存储空间中。

    <sys/wait.h>中定义了4个宏,用来获取进程的终止状态:

    WIFEXITED(status)标识正常终止;

    WIFSIGNALED(status)标识异常终止;

    WIFSTOPPED(status)标识当前暂停子进程;

    WIFCONTINUED(status)标识暂停后继续执行的子进程;

    关于参数pid:

    pid == -1: 等待任一子进程,此时waitpid与wait等效。

    pid > 0: 等待与pid相等的子进程。

    pid == 0: 等待组ID与调用进程组ID相等的任一子进程。

    pid < -1: 等待组ID等于pid绝对值的任一子进程。

    waitpid与wait的比较:

    (1)   waitpid可以等待一个特定的进程,而wait只能返回任一终止子进程的状态。

    (2)   waitpid提供了一个wait的非阻塞版本。

    (3)   waitpid通过WUNTRACED/WCONTINUED选项支持作业控制。

    [root@benxintuzi process]# cat wait01.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    
    void pr_exit(int status);
    
    int main(void)
    {
            pid_t   pid;
            int             status;
    
            if ((pid = fork()) < 0)
                    printf("fork error
    ");
            else if (pid == 0)                              /* child */
                    exit(7);
    
            if (wait(&status) != pid)               /* wait for child */
                    printf("wait error
    ");
            pr_exit(status);                                /* and print its status */
    
            if ((pid = fork()) < 0)
                    printf("fork error
    ");
            else if (pid == 0)                              /* child */
                    abort();                                        /* generates SIGABRT */
    
            if (wait(&status) != pid)               /* wait for child */
                    printf("wait error
    ");
            pr_exit(status);                                /* and print its status */
    
            if ((pid = fork()) < 0)
                    printf("fork error
    ");
            else if (pid == 0)                              /* child */
                    status /= 0;                            /* divide by 0 generates SIGFPE */
    
            if (wait(&status) != pid)               /* wait for child */
                    printf("wait error
    ");
            pr_exit(status);                                /* and print its status */
    
            exit(0);
    }
    
    void pr_exit(int status)
    {
            if (WIFEXITED(status))
                    printf("normal termination, exit status = %d
    ", WEXITSTATUS(status));
            else if (WIFSIGNALED(status))
                    printf("abnormal termination, signal number = %d
    ", WTERMSIG(status));
            else if (WIFSTOPPED(status))
                    printf("child stopped, signal number = %d
    ", WSTOPSIG(status));
    }
    
    [root@benxintuzi process]# ./wait01
    normal termination, exit status = 7
    abnormal termination, signal number = 6
    abnormal termination, signal number = 8

    另一个取得进程终止状态的函数是waitid,与waitpid相似,但waitid允许一个进程指定要等待的子进程。它使用两个参数表示要等待的子进程所属的类型。

    #include <sys/wait.h>

    int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

    返回值:成功,返回0;失败,返回-1

    说明:

    idtype参数可以如下:

    P_PID:等待一个特定进程,id中包含要等待子进程的进程ID。

    P_PGID:等待一个特定进程组的任一子进程,id中包含要等待子进程的进程组ID。

    P_ALL:等待任一子进程,此时忽略id。

    options参数如下(按位或):

    WCONTINUED等待一个进程,其曾被停止,然后又继续运行,但其状态尚未报告。

    WEXITED:等待已退出的进程。

    WNOHANG:如果没有可用的子进程退出状态,立即返回而非阻塞。

    WNOWAIT不破坏子进程退出状态,该子进程退出状态可由后续的wait/waitpid/waitid取得。

    WSTOPPED等待一个进程,它已经停止,但其状态尚未报告。

    注:

    必须指定WCONTINUED、WNOWAIT、WSTOPPED三者之一。

    infop指向一个siginfo结构,包含了造成子进程状态改变有关信号的详细信息。

    函数exec

    当进程调用exec函数时,exec就用磁盘上的一个新程序替换掉当前进程的正文段、数据段、堆区、栈区,然后开始执行新程序的main函数。一般调用fork后立即会调用exec,表示创建新进程后,立即在该进程中执行新程序。有7种exec函数可用:

    #include <unistd.h>

    int execl(const char* pathname, const char* arg0, ... /* (char*)0 */);

    int execv(const char* pathname, char* const argv[]);

    int execle(const char* pathname, const char* arg0, ... /* (char*)0, char* const envp[] */);

    int execve(const char* pathname, char* const argv[], char* const envp[]);

    int execlp(const char* filename, const char* arg0, ... /* (char*)0 */);

    int execvp(const char* filename, char* const argv[]);

    int fexecve(int fd, char* const argv[], char* const envp[]);

    返回值:成功,不返回;失败,返回-1

    说明:

    关于filename,如果filename中包含/,则将其视为路径名;否则就在PATH指定的目录中搜索可执行文件。

    如果execlp和execvp找到了一个可执行文件,但是该文件不是由链接器产生的可执行文件,则就将其看作一个shell脚本,试着调用/bin/sh,并将该filename作为shell的输入。

    函数名中的l表示列表list,v表示向量vector,e表示环境。因此execl/execlp/execle要求将新程序的每个命令行参数都作为一个独立的参数传递,而execv/execvp/execve/fexecve将其按数组方式传递。execle/execve/fexecve要求传递一个环境表指针,其他几个exec函数则使用全局变量environ为新程序复制现有的环境。

    首先,查看PATH变量,发现里面有HOME=/root一项,那么将以下可执行文件echoall放到/root目录下,echoall可执行文件源程序如下所示,用于打印当前进程的环境表:
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
            int                     i;
            char            **ptr;
            extern char     **environ;
    
            for (i = 0; i < argc; i++)              /* echo all command-line args */
                    printf("argv[%d]: %s
    ", i, argv[i]);
    
            for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
                    printf("%s
    ", *ptr);
    
            return 0;
    }
    
    然后使用fork创建新进程,使用exec函数运行echoall程序:
    [root@benxintuzi process]# cat exec01.c
    #include <unistd.h>
    #include <sys/wait.h>
    #include <stdio.h>
    
    char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };
    
    int main(void)
    {
            pid_t   pid;
    
            if ((pid = fork()) < 0) {
                    printf("fork error
    ");
            } else if (pid == 0) {  /* specify pathname, specify environment */
                    if (execle("/root/echoall", "echoall", "myarg1",
                                    "MY ARG2", (char *)0, env_init) < 0)
                            printf("execle error
    ");
            }
    
            if (waitpid(pid, NULL, 0) < 0)
                    printf("wait error
    ");
    
            if ((pid = fork()) < 0) {
                    printf("fork error
    ");
            } else if (pid == 0) {  /* specify filename, inherit environment */
                    if (execlp("/root/echoall", "echoall", "only 1 arg", (char *)0) < 0)
                            printf("execlp error
    ");
            }
    
            return 0;
    }
    
    [root@benxintuzi process]# ./exec01
    argv[0]: echoall
    argv[1]: myarg1
    argv[2]: MY ARG2
    USER=unknown
    PATH=/tmp
    [root@benxintuzi process]# argv[0]: echoall
    argv[1]: only 1 arg
    ...
    HOME=/root
    _=./exec01

     

    更改用户ID和组ID

    Unix系统中,访问控制是基于用户ID和组ID的,当程序需要访问当前并不允许访问的资源时,就需要增加特权,更换自己的用户ID和组ID,使之具有合适的特权或访问权限。

    可以用setuid函数设置实际用户ID和有效用户ID,用setgid设置实际组ID和有效组ID。关于内核维护的3个用户ID,有如下说明:

    (1)   只有超级用户进程才可以更改实际用户ID。通常,实际用户ID是在登录时,由login程序设置的,而login是一个超级用户进程,当它调用setuid时,设置所有3个用户ID。

    (2)   仅当文件中设置了保存用户ID位时,exec函数才会设置有效用户ID。如果保存用户ID位没有设置,则exec函数不会改变有效用户ID,而是维持其现有值不变。任何时候都可以调用setuid将有效用户ID设置为实际用户ID或者保存用户ID。

    (3)   保存用户ID是由exec函数复制有效用户ID而得到的。如果设置了文件的保存用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID后,这个副本就被保存起来了。

    如下函数用户交换实际ID和有效ID的值:

    #include <unistd.h>

    int setreuid(uid_t ruid, uid_t euid);

    int setregid(gid_t rgid, gid_t egid);

    返回值:成功,返回0;失败,返回-1

    说明:

    一个非特权用户可将其有效用户ID设置为实际用户ID或者保存用户ID,一个特权用户则可将有效用户ID设置为uid,设置不同用户ID的函数图示如下:

     

    解释器文件

    解释器文件的起始行形式是:

    #! pathname [optional-argument]

    在!和pathname之间的空格是可选的,最常见的解释器文件开始行为:

    #! /bin/sh

    pathname通常是绝对路径名。对解释器文件的处理是由内核作为exec系统调用的一部分来完成的。内核使调用exec函数的进程实际执行的并不是解释器文件本身,而是解释器文件中第一行pathname指定的文件,如下所示:

    [root@benxintuzi process]# cat /root/echoall.c
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
            int                     i;
            char            **ptr;
            extern char     **environ;
    
            for (i = 0; i < argc; i++)              /* echo all command-line args */
                    printf("argv[%d]: %s
    ", i, argv[i]);
    
            for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
                    printf("%s
    ", *ptr);
    
            return 0;
    }
    
    [root@benxintuzi process]# cat /root/testinterp
    #! /root/echoall echoarg1 echoarg2
    
    [root@benxintuzi process]# cat interp.c
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/wait.h>
    
    int main(void)
    {
            pid_t pid;
    
            if ((pid = fork()) < 0)
                    printf("fork error
    ");
            else if (pid == 0)
                    if (execl("/root/echoall", "/root/testinterp", "myarg1", "myarg2", (char*)0) < 0)
                            printf("execl error
    ");
            if (waitpid(pid, NULL, 0) < 0)
                    printf("waitpid error
    ");
    
            return 0;
    }
    
    [root@benxintuzi process]# ./interp
    argv[0]: /root/testinterp
    argv[1]: myarg1
    argv[2]: myarg2
    HOSTNAME=benxintuzi
    SELINUX_ROLE_REQUESTED=
    SHELL=/bin/bash
    TERM=vt100
    HISTSIZE=1000
    HADOOP_HOME=/bigdata/hadoop-2.6.0
    SSH_CLIENT=192.168.8.1 50293 22
    SELINUX_USE_CURRENT_RANGE=
    QTDIR=/usr/lib/qt-3.3
    QTINC=/usr/lib/qt-3.3/include
    SSH_TTY=/dev/pts/0
    USER=benxintuzi

    进程调度

    调度策略和调度优先级是由内核控制的,进程可以通过调整nice值降低进程的调度优先级。nice越小,优先级越高。进程可以通过nice函数获取或更改其nice值,该操作只影响进程自己的nice值,不影响其他进程:

    #include <unistd.h>

    int nice(int lchr);

    返回值:成功,返回新的nice值NZERO;失败,返回-1

    说明:

    nice值的范围一般为0~(2*NAERO) – 1。

    新的nice值new_nice = old_nice + lchr,如果超出nice值的范围,自动降到最大、最小合法值。

    #include <sys/resource.h>

    int getpriority(int which, id_t who);

    返回值:成功,返回-NZERO~NZERO – 1之间的nice值;失败,返回-1

    说明:

    getpriority函数不仅可以获取进程的nice值,还可以获取一组相关进程的nice值。

    which参数如下:

    PRIO_PROCESS:表示进程。

    PRIO_PGRP:表示进程组。

    PRIO_USER:表示用户ID。

    who参数选择一个或者多个进程,如果为0,表示选择一个。

    #include <sys/resource.h>

    int setpriority(int which, id_t who, int value);

    返回值:成功,返回0;失败,返回-1

    进程时间

    #include <sys/times.h>

    clock_t times(struct tms* buf);

    返回值:成功,返回时间;失败,返回-1

    说明:

    times函数填充tms结构体:

    struct tms

    {

        clock_t    tms_utime; /* user CPU time */

        clock_t tms_stime;   /* system CPU time */

        clock_t tms_cutime;  /* user CPU time, terminated children */

        clock_t tms_cstime;  /* system CPU time, terminated children */

    };

    sysconf(_SC_CLK_TCK)返回每秒时钟的滴答数。

    如下函数将命令行参数作为程序运行,并且统计执行每个程序的时间:

    [root@benxintuzi process]# cat times.c
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/times.h>
    
    static void     pr_times(clock_t, struct tms *, struct tms *);
    static void     do_cmd(char *);
    
    int main(int argc, char *argv[])
    {
            int             i;
    
            setbuf(stdout, NULL);
            for (i = 1; i < argc; i++)
                    do_cmd(argv[i]);        /* once for each command-line arg */
            return 0;
    }
    
    static void do_cmd(char *cmd)           /* execute and time the "cmd" */
    {
            struct tms      tmsstart, tmsend;
            clock_t         start, end;
            int                     status;
    
            printf("
    command: %s
    ", cmd);
    
            if ((start = times(&tmsstart)) == -1)   /* starting values */
                    printf("times error
    ");
    
            if ((status = system(cmd)) < 0)                 /* execute command */
                    printf("system() error
    ");
    
            if ((end = times(&tmsend)) == -1)               /* ending values */
                    printf("times error
    ");
    
            pr_times(end-start, &tmsstart, &tmsend);
            printf("status = %d
    ", status);
    }
    
    static void pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
    {
            static long             clktck = 0;
    
            if (clktck == 0)        /* fetch clock ticks per second first time */
                    if ((clktck = sysconf(_SC_CLK_TCK)) < 0)
                            printf("sysconf error
    ");
    
            printf("  real:  %7.2f
    ", real / (double) clktck);
            printf("  user:  %7.2f
    ",
              (tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
            printf("  sys:   %7.2f
    ",
              (tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
            printf("  child user:  %7.2f
    ",
              (tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
            printf("  child sys:   %7.2f
    ",
              (tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
    }
    
    [root@benxintuzi process]# ./times "sleep 5" "date" "man bash > /dev/null"
    
    command: sleep 5
      real:     5.03
      user:     0.00
      sys:      0.00
      child user:     0.00
      child sys:      0.00
    status = 0
    
    command: date
    Sat Aug 29 20:26:49 PDT 2015
      real:     0.01
      user:     0.00
      sys:      0.00
      child user:     0.00
      child sys:      0.01
    status = 0
    
    command: man bash > /dev/null
      real:     0.69
      user:     0.00
      sys:      0.00
      child user:     0.19
      child sys:      0.49
    status = 0
    [root@benxintuzi process]#

     

  • 相关阅读:
    linux引导系统
    Android开发面试经——2.常见Android基础笔试题
    Android开发面试经——1.常见人事面试问题
    Android面试题整理【转载】
    android设置软键盘搜索键以及监听搜索键点击时发生两次事件的问题解决
    Android软键盘弹出时把布局顶上去的解决方法
    Android入门:绑定本地服务
    Android aidl Binder框架浅析
    Android LayoutInflater深度解析 给你带来全新的认识
    Android RecyclerView 使用完全解析 体验艺术般的控件
  • 原文地址:https://www.cnblogs.com/benxintuzi/p/4770589.html
Copyright © 2011-2022 走看看