进程是什么想必大家都知道吧,简单的说就是动起来的程序,程序放到内存中去执行就成为了进程了。进程有三种状态:就绪、阻塞、执行。下面我们来看看linux下关于进程的一些命令和系统调用吧。

ps -ef 这个是在unix/linux下查看系统中运行的进程的shell命令。 
ps -aux linux 这个是在linux下查看系统运行的进程的命令,和上边的那个命令都可以。
我们使用上边的命令查看进程的时候,在STAT的字段下标出的是进程的状态,常见进程的状态如下:
S 休眠状态
R 运行状态
O 可运行状态
Z 僵尸进程

进程ID(进程的唯一标识)
getpid()这个函数可以获取当前进程的ID号。
getppid()获取父进程的ID号。有一个特殊进程是init,它的进程号是1,但父进程先运行结束后,子进程就成为了孤儿进程,这个时候就把init进程作为父进程,大家会在接下来的代码中看到的。

创建子进程。在程序中调用fork函数的时候我们会创建出一个子进程来。下面就来看看这个函数。
pid_t fork(void); 一次调用,两次返回。
在父进程中,返回新创建子进程的ID
在子进程中返回0
如果出现错误,返回-1
以上的说法不理解没有关系,我们会通过代码来说明。

注意:fork()函数创建子进程,复制父进程代码之外的内存区域,和父进程完全共享代码段。意思就是说子进程的全局区,栈区,堆区都是自己独有的,修改这里边的变量,不会影响到父进程。
fork创建子进程之后,规范中没有确定谁先运行,linux操作系统中父子进程执行顺序是不确定的。

#include //fork函数的头文件
#include

int main()
{
    pid_t pid;
	//创建子进程,执行以下这句话以后就有了俩个程序在运行
    pid=fork();
    //此时有两个进程
	//返回-1代表创建进程失败
    if(pid<0)
        printf("error fork!");
	//返回0的代表子进程,所以子进程会执行printf("I am child proess,id is %d\n",getpid());
    else if(pid==0)
        printf("I am child proess,id is %d,my parent id:%d\n",getpid(),getppid());
    else
        printf("I am parent proess,id is %d,my child id:%d\n",getpid(),pid);
    return 0;
}

创建进程

进程等待
pid_t wait(int *status)
等待任意一个子进程结束,status会保存子进程的结束信息(退出码判断是否正常退出),返回值就是子进程的ID。它就会导致父进程一直阻塞,直到到有一个子进程结束(包括僵尸子进程)
WEXTSTATUS(status)//用来取出退出码,函数的具体用法如下。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h> //包含系统的基本数据类型,像size_t time_t pid_t
#include <sys/wait.h> //wait函数的头文件
#include <errno.h> //errno全局变量的头文件

int main()
{
    pid_t pid;
    pid_t wait_pid;
    int status;

    pid = fork();
    if(pid == 0)
    {
        printf("this is a child process with pid is:%d\n",getpid());
        sleep(5);
        exit(1); //设置子进程的退出码为1
    }
    else if(pid > 0)
    {
        wait_pid = wait(&status); //将退出码保存在status中,返回等待退出的子进程的pid
		printf("i catch a child process with pid is:%d,and exit number is:%d\n",wait_pid,WEXITSTATUS(status));
    }
    else
    {
        fprintf(stderr,"error:%s",(char *)strerror(errno));
    }

    return 0;
}

linux下的进程编程

5.exec函数族
子进程调用exec系列函数(就是说这些函数的调用一般放在子进程中去调用),从而获取全新内存空间运行一个全新的程序,运行起来这个全新的程序以后,原来子进程中的剩下的还没运行完的语句就不执行了。平时的编程中,如果用到了exec 函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易受伤。

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

int main()
{

    if(fork() == 0)
    {
		//execl的第一个参数是包括路径的可执行文件,后面是列表参数,列表的第一个为命令,接着为参数列表,最后必须以NULL结束。
        if(execl("/home/tarena/workdir/demo3/exec","exec",NULL) == -1)
        {
			//perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 错误 (stderr) 。
			//参数 str 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
            perror("execl failed!");
			//以下的语句和上边的是等同的
            fprintf(stderr,"execl failed! %s\n",(char *)strerror(errno));
        }
        exit(1);
    }

    if(fork() == 0)
    {
		//execlp的第一个参数可以使用相对路径或者绝对路径。
        if(execlp("echo","echo","Hello",NULL) == -1)
        {
            perror("execlp failed!");
		}
        exit(1);
    }

    char * argv[] = {"ls","-l",".",NULL};

    if(fork() == 0)
    {
		//第一个参数是路径,第二个参数是数组
        if(execv("/bin/ls",argv) == -1)
        {
            perror("execv failed!");
        }
    }

    return 0;
}

linux下的进程编程
6.system(const char *),这个函数就相当于在shell下执行了命令,在win32编程中我们也经常的用到,就相当于在dos窗口下敲命令。实例代码如下。

#include<stdio.h>

int main()
{
    system("ls -al");
    return 0;
}

linux下的进程编程

我们再来说一下进程通信的问题,信号和共享内存。首先来看信号。信号的本质是一个整数,系统用信号实现中断。宏名是SIGXXX。常见信号有ctrl+c(SIGINT)、ctrl+\(SIGQUIT),我们可以使用kill -l 命令来察看系统都有哪些信号,使用kill -s SIGINT xxx命令来结束xxx进程,xxx代表的是进程的ID。

linux下的进程编程

下面我们就来看一下signal函数如何使用,这个函数用在程序中的时候,是为程序注册信号用的,函数有俩个参数,一个是信号的宏,就是SIGXXX形式的宏,这个宏不可以是SIGKILL和SIGSTOP信号,也就是数字9和19的信号,因为这俩个信号是不可以被忽略和自己重定义处理方式的。另一个参数可以有三种情况,可以是一个函数指针,在函数的实现里边可以写自己的处理方式;可以是SIG_IGN宏,代表该程序将忽略前边的那个类型的信号,当程序捕捉到这个信号的时候就不处理;可以是SIG_DFL,表示的是采用系统的默认处理方式,系统的默认处理方式一般都是中断信号的。这个函数没有返回值。现在我们就通过具体的代码来看看如何使用它吧。

#include <stdio.h>
#include <signal.h> //signal函数的头文件
#include <stdlib.h>

//信号处理函数,这个函数的参数必须是int类型的,用来接收信号,返回值是void
void sg(int sig)
{
    printf("捕获了信号:%d\n",sig);
}

int main()
{

    //注册了SIGINT信号,当该程序收到这个信号的时候,就会调用sg函数了,如果没有注册成功的话,就执行下边的语句了
    if(signal(SIGINT,sg) == SIG_ERR)
        perror("sigint"),exit(-1);
    //注册SIGQUIT信号,当程序收到这个信号的时候,会忽略掉这个信号的,当然还可以注册为默认的类型,这个时候系统会
    //采用默认的处理方式的
    if(signal(SIGQUIT,SIG_IGN) == SIG_ERR)
        perror("sigquit"),exit(-1);

   //这里之所以加个死循环是因为上边的程序代码是用来注册的,当程序执行到这里的时候就不走了,以便我们给这个程序传递信号
    while(1);
    return 0;
}

linux进程编程

接下来再说一下共享内存,共享内存也是用来进程间相互进行通信的一种方法。每个进程都有自己的虚拟内存空间,共享内存就是开辟一个物理内存,这个物理内存可以分别映射到各个进程自己的内存空间上,相当于各个进程都拥有这块内存空间,这就叫做共享内存。当一个进程改变了这块空间中的内容的时候,其他进程在这块空间上取数的时候,取出来的当然就是改变后的值了。现在来看看用到些什么函数吧。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h> //ipc代表的是进程通信,这个是进程通信的一些函数的头文件

int main()
{

    int id;
	//shmget获得一块共享内存,第一个参数需要一个非0的整数,第二个参数是共享内存的大小
	//第三个参数是创建的权限,类似文件操作的函数open里边的参数,返回值是共享内存描述符
    id = shmget(100,sizeof(int),IPC_CREAT | 0664);
    if(id  == -1)
    {
        perror("shmget:"),exit(-1);
    }
    printf("shmget secceed,and id is:%d\n",id);

    int * pid;
	//与共享内存挂接,需要传入的参数是共享内存描述符,第二个和第三个参数一般都传入0,返回值是一个指针
    pid = (int *)shmat(id,0,0);
    if(pid == (int *)-1)
        perror("shmat:"),exit(-1);
    printf("挂接成功!\n");

	//改变共享内存中的值
    *pid = 100;
    printf("内存中的数为:%d\n",*pid);
	//与共享内存脱接
	shmdt(id);
    }

	//创建子进程
    if(fork() == 0)
    {
		//挂接
       int * pfork =  (int *)shmat(id,0,0);
       if(pfork == (int *)-1)
       {
            perror("pfork"),exit(-1);
       }
       printf("子进程挂接成功!\n");
	   //改变共享内存中的值
        *pfork = 1000;
        printf("内存中的数为:%d\n",*pid);
		//脱接
        shmdt(id);
    }

    return 0;
}

linux程序设计进程编程