C语言的多进程(3)

欢迎加入QQ:498903810 一起交流、讨论知识,里面有大佬,也有小白,天下码农一家亲,大家一起讨论进步。

优化ls -l的获取权限方式点击查看

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{

    int j = 0;

    struct stat st;
    if(stat("stat.c", &st) < 0)
    {
        perror("stat");
        return -1;
    }
    for(j = 8; j >= 0; j--)
    {
        if(st.st_mode & (1 << j))
        {
            if(j % 3 == 2)
                printf("r");
            else if(j % 3 == 1)
                printf("w");
            else if(j % 3 == 0)
                printf("x");
        }
        else
            printf("-");
    }

    return 0;
}

进程来啦!!!点击获取源代码

main()函数,进程的准备

1.main 函数

一个程序运行起来就是一个进程,我们真实的代码是先进行链接,就是把C文件各个段进行组合,这个过程需要连接器,为我们作引导此时也就链接到我们的*.o文件里(其中最重要的是main()函数里面的参数,就是此时进入目标文件)。接着就需要加载器,将我们的代码加载进固定的虚拟地址。这个就是一个进程的完整步骤

2.程序的终止

(1)程序正常终止:

​ 方式:exit()_exit()return

exit():C语言库,会在程序结束之前进行缓冲区的清除;

_exit():系统API,不会在程序结束之前进行缓冲区的清除,直接推出进程。

#include 

int dup(int oldfd);//返回一个新的文件描述符,成功返回描述符,失败返回-1
int dup2(int oldfd, int newfd);//指定新的文件描述符,成功返回的描述,失败返回-1

dup2指定新的描述符,如果指定的描述符已经被占用,就把之前已经占用的描述符关闭,分配自己指定的描述符。

我们的printf()也是写文件,不过是写到流文件描述符是(1,1是标准输出)的那个文件。

alarm()函数
//头文件
#include 

//函数
unsigned int alarm(unsigned int seconds);

//描述
 alarm()  arranges  for  a SIGALRM signal to be delivered to the calling process in seconds seconds.

If seconds is zero, any pending alarm is canceled.

In any event any previously set alarm() is canceled.
signal()函数
头文件
#include 

//函数指针
typedef void (*sighandler_t)(int);

//函数原型
sighandler_t signal(int signum, sighandler_t handler);


例:
#include 
#include 
#include 

//真实的信号处理函数
//typedef void (*sighandler_t)(int); 函数指针
void func(int sig)//回调函数
{
    //printf("sig = %d\n", sig);
    if(sig == 14)
        printf("hello world\n");

}

int main()
{
    //操作系统2S之后会发一个信号SIGALRM给你的当前进程
    alarm(2);

    //信号处理函数 kill -l 查看宏定义
    //SIGALRM是一个宏定义 == 14
    //signal(14, func)//也可以
    signal(SIGALRM, func);//回调函数的必须无返回值,有返回值会报错

    sleep(3);//睡眠3秒,等待系统给你的信号
    printf("int main\n");
    return 0;
}

kill -l 命令查看宏定义。

(2)程序异常终止:

发信号进行终止,外界把你终止了。杀死父进程,子进程也就结束了。

3.注册进程终止函数atexit()

目的就是:当你看到项目(BSP)时,别人时怎么写的,你就照着写,出现的什么场合,你要结合上下文。可以有多个。

多个注册进程终止函数的调用是符合栈特性的,先调用后执行。

//头文件
#include 

//函数原型
int atexit(void (*function)(void));

//返回值
The  atexit()  function returns the value 0 if successful; otherwise it returns a nonzero value.

例:
#include 
#include 

void func()
{
    printf("进程终止函数起作用\n");
}

int main(void)
{
    int i = 1;
    int ret = atexit(func);

    if(i > 0)
    {
        printf("int main\n");
        exit(0);//结束进程
    }
    return 0;
}
运行结果:
int main
进程终止函数起作用

4.环境变量

目前理解:environ这个变量,名字是固定的不可更改。操作系统的全局变量,有一个变量environ就是存储所有的环境变量。每个进程执行时会复制一份环境变量表来自己用。

echo $PATH//打印环境变量

export//打印所有的环境变量

environ这个变量,是操作系统的一个真实变量,可以用的。所有的环境变量都存在这个数组中,字符串数组。

#include 

extern char **environ;

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

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

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

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

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

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

更改之后的环境变量,值在当前进程起作用。

#include 

extern char **environ;

int main(void)
{
    int i = 0;
    while(NULL != environ[i])//打印环境变量
    {
        printf("%s\n",environ[i]);
        i++;
    }
    return 0;
}

getenv()函数

//头文件
#include 

//函数原型
char *getenv(const char *name);

char *secure_getenv(const char *name);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

secure_getenv(): _GNU_SOURCE

5.虚拟内存和物理内存

1.概念

(1)每个进程都是在独立的内存空间运行。

(2)逻辑上的内存空间0~4G,指的就是虚拟内存。

(3)虚拟内存和物理内存之间存在映射影射关系的。

(4)操作系统调度各个进程彼此之间,互不影响,彼此看不见。

操作OS:让某个进程先运行或后运行,是有一套算法的,进程任务的轻重缓急来处理。

6.进程怎么样产生

太极很像:一生二,二生四,四生万物。

​ 父进程—>子进程—>子进程

有特殊的进程:孤儿进程、僵尸进程。

7.进程ID号

进程ID:一旦使用后,下一次即使是同一个程序运行依旧分配新的进程号,所以这里显示的是,目前为止,这个系统打开过的所有进程。

8.一个进程结束后发生的事

一个进程结束后,操作OS会回收资源(CPU时间片、各种内存都释放(堆、栈、数据区… …))。

如:wait

创建进程

fork()函数

子进程会复制父进程的数据(堆、栈、数据区)

pid_t pid;//pid_t是一个无符号整形的宏定义

pid = fork();//创建一个新进程,在这句代码之前都是父进程的东西。

操作OS开始调度这两个进程,谁先谁后时没有规定,进程之间是独立的。

pa -aux //查看系统的所有进程

fork()函数工作流程

一次调用返回两个效果(一个是父进程、一个是子进程)

此处代码笔记不好整理,希望读者可以自己敲一下,避免混淆。

#include 
#include 
#include 

int main()
{
    //进程ID号
    pid_t pid;
    int num = 0;

    //printf("hello fork\n");//打印一次,有'\n'时,缓冲区的数据回到,输出控制台上。
    //printf("hello fork");//打印两次 ---》在父进程里面加sleep(2)可以看到效果.

    /*
    没有换行,字符串在缓冲区,子进程复制了父进程缓冲区里面的内容。
    printf("hello fork");
    加了换行之后,字符会在输出到控制台,所以之后打印一次。
    创建一个新的进程,在这句代码之前都是父进程的东西。
    复制了缓冲区,所以才会打印两次。
    */

    //创建一个新进程   

    //操作OS开始调度这两个进程,谁先谁后时没有规定。进程之间是独立的。

    pid = fork();//从此子进程拷贝一份父进程开始执行。
    if(pid > 0)//区分子进程和父进程
    {
        //父进程
        //sleep(2);
        num++;
        //printf("父进程\n");
        /*
            getpid()函数,得到当前进程的PID号。
            getppid()函数,得到当前进程的父进程的PID号。
        */
        printf("父进程:pid = %d, getpid = %d, getppid() = %d\n",pid, getpid(), getppid());
    }
    else if(pid == 0)//子进程复制了父进程的数据(堆、栈、数据区)//区分子进程和父进程
    {
        //sleep(2);
        //子进程
        num++;
        //printf("子进程\n");
        printf("子进程: pid = %d, getpid = %d, getppid() = %d\n",pid, getpid(), getppid());
    }
    else
    {
        perror("fork");
        exit(0);
    }
    //printf("hello num = %d\n",num);
    return 0;
}
//头文件
#include 

//函数原型
pid_t fork(void);

//返回值
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.

//描述 ---> 重要
fork()  creates  a new process by duplicating the calling process.  The
new process, referred to as the child, is an  exact  duplicate  of  the
calling  process,  referred  to as the parent, except for the following
points:

*  The child has its own unique process ID, and this PID does not match
the ID of any existing process group (setpgid(2)).

*  The  child's  parent  process ID is the same as the parent's process
ID.

//返回值
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.

getpid()getppid()函数

getpid()函数,得到当前进程的PID号。

getppid()函数,得到当前进程的父进程的PID号。

//头文件
#include 
#include 

//函数原型
pid_t getpid(void);
pid_t getppid(void);

//描述
getpid() returns the process ID of the calling process.  (This is often
used by routines that generate unique temporary filenames.)
getppid() returns the process ID of the parent of the calling process.

//返回值和错误
These functions are always successful.

fork()和文件操作函数open()

open()函数用法查看之前的博客

//子进程复制父进程实现写操作,子进程文件指针和父进程的文件指针相互影响同步
//父进程和子进程的文件指针关联起来,共享一个文件,实现了分别写文件,
#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
    int fd = -1;
    pid_t pid;
    fd = open("1.txt", O_RDWR | O_TRUNC);
    if(fd < 0)
    {
        perror("open");
        exit(0);
    }
    //建立一个子进程
    pid = fork();
    if(pid > 0)//父进程
    {
        write(fd, "abc", 3);
    }
    else if(0 == pid)//子进程
    {
        write(fd, "123", 3);
        /*
        父进程和子进程的文件指针关联起来,共享一个文件,实现了分别写文件。
        */
    }
    else
    {
        perror("fork");
    }
    return 0;
}

运行结果:1.txt文件的内容为:
abc123
//父子进程打开同一个文件,进行写操作
//如果打开文件的时候加 O_APPEND 的时候也可以实现两个文件关联
#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
    int fd = -1;
    pid_t pid;

    //建立一个子进程
    pid = fork();
    if(pid > 0)//父进程
    {
        fd = open("1.txt", O_RDWR);//不能加O_TRUNC
        if(fd < 0)
        {
            perror("open");
            exit(0);
        }
        printf("父:fd = %d\n", fd);
        write(fd, "abc", 3);
    }
    else if(0 == pid)//子进程
    {
        fd = open("1.txt", O_RDWR);//不能加O_TRUNC
        if(fd < 0)
        {
            perror("open");
            exit(0);
        }
        printf("子:fd = %d\n", fd);
        write(fd, "123", 3);
    }
    else
    {
        perror("fork");
    }
    return 0;
}

运行结果:1.txt文件的内容为:
123

多进程实现鼠标和键盘的同时输入

必须要用root用户执行,否则鼠标读写有问题。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//多进程实现读鼠标和键盘
//必须要用root用户执行,否则鼠标读写有问题。
int main()
{
    char buf[100]; //buf空间的来源:1、栈 2、堆 3、数据区(即位全局变量)
    int fd = -1;
    pid_t pid;

    pid = fork();

    if(pid == 0)
    {
        while(1)
        {
            bzero(buf, 0);
            read(0, buf, 5);
            printf("键盘得到的内容:%s\n", buf);    
        }
    }
    else if(pid > 0)
    {
        fd = open("/dev/input/mouse0", O_RDWR);
        while(1)
        {
            read(fd, buf, 5);
            printf("鼠标得到的内容:%s\n", buf);
        }
    }
    else 
    {
        perror("fork");
        exit(0);
    }

    return 0;
}

wait()函数

作用:父进程等待子进程死后才会执行。

//头文件
#include 
#include 

//函数原型
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, 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) speci‐
fied by pid exist, but have not yet changed state, then 0 is  returned.
On error, -1 is returned.

waitid():  returns  0  on  success  or  if WNOHANG was specified and no
child(ren) specified by id has yet  changed  state;  on  error,  -1  is
returned.   Each  of  these calls sets errno to an appropriate value in
the case of an error.
#include 
#include 
#include 
#include 

int main()
{
    int status = 0;
    int fd = -1;
    pid_t pid;

    pid = fork();

    if(pid > 0)
    {
        wait(&status);//status,0表示假(子进程非正常推出),1表示真(子进程正常推出)

        printf("父进程 status = %d\n", WIFEXITED(status));
    }
    else if(pid == 0)
    {
        printf("子进程\n");
    }
    else 
    {
        perror("fork");
        exit(0);
    }

    return 0;
}

你可能感兴趣的:(C语言,Linux,C/C++开发工程师之路)