All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jan Stancek <jstancek@redhat.com>
To: ltp@lists.linux.it
Subject: [LTP] [PATCH v2] mm: rewrite mtest01 with new API
Date: Thu, 7 Mar 2019 04:57:15 -0500 (EST)	[thread overview]
Message-ID: <997663519.5833721.1551952635690.JavaMail.zimbra@redhat.com> (raw)
In-Reply-To: <20190306095109.13868-1-liwang@redhat.com>


----- Original Message -----
> Test issue:
>    mtest01 start many children to alloc chunck of memory and do write
>    page(with -w option), but occasionally some children were killed by
>    oom-killer and exit with SIGCHLD signal sending. After the parent
>    reciving this SIGCHLD signal it will report FAIL as a test result.
> 
>    It seems not a real kernel bug if something just like that, it's
>    trying to use 80% of memory and swap. Once it uses most of memory,
>    system starts swapping, but the test is likely consuming memory at
>    greater rate than kswapd can provide, which eventually triggers OOM.
> 
>    ---- FAIL LOG ----
>    mtest01     0  TINFO  :  Total memory already used on system = 1027392
>    kbytes
>    mtest01     0  TINFO  :  Total memory used needed to reach maximum =
>    12715520 kbytes
>    mtest01     0  TINFO  :  Filling up 80% of ram which is 11688128 kbytes
>    mtest01     1  TFAIL  :  mtest01.c:314: child process exited unexpectedly
>    -------------------
> 
>  Rewrite changes:
>    To make mtest01 more easier to understand, I just rewrite it into
>    LTP new API and make a little changes in children behavior.
> 
>    * decrease the pressure to 80% of free memory for testing
>    * drop the signal SIGCHLD action becasue new API help to
>    check_child_status
>    * make child pause itself after finishing their memory allocating/writing
>    * parent sends SIGCONT to make children continue and exit
>    * use TST_PROCESS_STATE_WAIT to wait child changes to 'T' state
> 
> Signed-off-by: Li Wang <liwang@redhat.com>
> ---
> 
> Notes:
>     v1 --> v2
>        * allocate 80% of free RAM for testing
>        * remove the pre_free - post_free check
>        * involve tst_time_remaining for safe exit from loop
>        * use TST_PROCESS_STATE_WAIT to wait child state change
> 
>  testcases/kernel/mem/mtest01/mtest01.c | 435 +++++++++++--------------
>  1 file changed, 198 insertions(+), 237 deletions(-)
> 
> diff --git a/testcases/kernel/mem/mtest01/mtest01.c
> b/testcases/kernel/mem/mtest01/mtest01.c
> index ca9073a8e..0f1743c64 100644
> --- a/testcases/kernel/mem/mtest01/mtest01.c
> +++ b/testcases/kernel/mem/mtest01/mtest01.c
> @@ -1,36 +1,23 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
>  /*
> + *  Copyright (c) International Business Machines Corp., 2001
> + *  Copyright (c) Linux Test Project., 2019
>   *
> - *   Copyright (c) International Business Machines  Corp., 2001
> + *  DESCRIPTION:
>   *
> - *   This program is free software;  you can redistribute it and/or modify
> - *   it under the terms of the GNU General Public License as published by
> - *   the Free Software Foundation; either version 2 of the License, or
> - *   (at your option) any later version.
> + *  mtest01 mallocs memory <chunksize> at a time until malloc fails.
>   *
> - *   This program is distributed in the hope that it will be useful,
> - *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
> - *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
> - *   the GNU General Public License for more details.
> - *
> - *   You should have received a copy of the GNU General Public License
> - *   along with this program;  if not, write to the Free Software
> - *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA
> - */
> -
> -/*
> - *  FILE		: mtest01.c
> - *  DESCRIPTION : mallocs memory <chunksize> at a time until malloc fails.
> - *  HISTORY:
> - *	04/10/2001 Paul Larson (plars@us.ibm.com)
> - *	  written
> - *	11/09/2001 Manoj Iyer (manjo@austin.ibm.com)
> - *	  Modified.
> - *	  - Removed compile warnings.
> - *	  - Added header file #include <unistd.h> definition for getopt()
> - *	05/13/2003 Robbie Williamson (robbiew@us.ibm.com)
> - *	  Modified.
> - *	  - Rewrote the test to be able to execute on large memory machines.
> + *  Parent process starts several child processes (each child process is
> + *  tasked with allocating some amount of memory), it waits until all child
> + *  processes send SIGRTMIN signal or until the amount of free memory is
> + *  decreased by the amount that all child tasks together should allocate,

Hi,

This part doesn't look accurate for v2, it doesn't check free memory.

> + *  it resumes all children by sending the SIGCONT signal.
>   *
> + *  Child process allocates certain amount of memory and fills it with some
> + *  data (the '-w' option) so the pages are actually allocated when the
> desired
> + *  amount of memory is allocated then it sends SIGRTMIN signal to the
> parent
> + *  process, it pauses itself by raise SIGSTOP until get parent SIGCONT
> signal
> + *  to continue and exit.
>   */
>  
>  #include <sys/types.h>
> @@ -42,23 +29,87 @@
>  #include <stdlib.h>
>  #include <unistd.h>
>  
> -#include "test.h"
> +#include "tst_test.h"
>  
> -#define FIVE_HUNDRED_MB (unsigned long long)(500*1024*1024)
> -#define ONE_GB	(unsigned long long)(1024*1024*1024)
> -#define THREE_GB (unsigned long long)(3*ONE_GB)
> +#define FIVE_HUNDRED_MB	(unsigned long long)(500*1024*1024)
> +#define ONE_GB		(unsigned long long)(1024*1024*1024)
> +#define THREE_GB	(unsigned long long)(3*ONE_GB)
> +#define STOP_THRESHOLD 15 /* seconds remaining before reaching timeout */
>  
> -char *TCID = "mtest01";
> -int TST_TOTAL = 1;
> -static sig_atomic_t pid_count;
> -static sig_atomic_t sigchld_count;
>  static pid_t *pid_list;
> +static sig_atomic_t pid_count;
> +static struct sysinfo sstats;
> +static int max_pids;
> +static unsigned long long total_free;
> +static unsigned long long alloc_bytes;
> +static unsigned long long alloc_maxbytes;
> +static unsigned long long original_maxbytes;

I wonder which of these need to be global.

> +
> +static int chunksize = 1024*1024;
> +static int maxpercent = 20;
> +static long maxbytes = 0;
> +static char *dowrite;
> +static char *verbose;
> +
> +static char *opt_chunksize, *opt_maxbytes, *opt_maxpercent;
> +static struct tst_option mtest_options[] = {
> +	{"c:", &opt_chunksize,	"-c  size of chunk in bytes to malloc on each
> pass"},
> +	{"b:", &opt_maxbytes,	"-b  maximum number of bytes to allocate before
> stopping"},
> +	{"p:", &opt_maxpercent, "-u  percent of total memory used at which the
> program stops"},
> +	{"w",  &dowrite,   	"-w  write to the memory after allocating"},
> +	{"v",  &verbose,     	"-v  verbose"},
> +	{NULL, NULL, 		NULL}
> +};
>  
> -static void handler(int signo)
> +static void parse_mtest_options(char *str_chunksize, int *chunksize,
> +		char *str_maxbytes, long *maxbytes,
> +		char *str_maxpercent, int *maxpercent)
>  {
> -	if (signo == SIGCHLD)
> -		sigchld_count++;
> -	pid_count++;
> +	if (str_chunksize)
> +		if (tst_parse_int(str_chunksize, chunksize, 1, INT_MAX))
> +			tst_brk(TBROK, "Invalid chunksize '%s'", str_chunksize);
> +
> +	if (str_maxbytes) {
> +		if (tst_parse_long(str_maxbytes, maxbytes, 1, LONG_MAX)) {
> +			tst_brk(TBROK, "Invalid maxbytes '%s'", str_maxbytes);
> +		} else if (str_maxpercent) {
> +			tst_brk(TBROK, "ERROR: -b option cannot be used with -p "
> +					"option at the same time");
> +		}
> +		alloc_maxbytes = (unsigned long long)maxbytes;
> +	}
> +
> +	if (str_maxpercent) {
> +		if (tst_parse_int(str_maxpercent, maxpercent, 1, 99)) {
> +			tst_brk(TBROK, "Invalid maxpercent '%s'", str_maxpercent);
> +		} else if (str_maxbytes) {
> +			tst_brk(TBROK, "ERROR: -p option cannot be used with -b "
> +					"option at the same time");
> +		}
> +	}
> +}
> +
> +static void setup(void)
> +{
> +	parse_mtest_options(opt_chunksize, &chunksize,
> +			opt_maxbytes, &maxbytes,
> +			opt_maxpercent, &maxpercent);
> +	sysinfo(&sstats);
> +	total_free = sstats.freeram;
> +
> +	max_pids = total_free * sstats.mem_unit
> +		/ (unsigned long)FIVE_HUNDRED_MB + 10;
> +	pid_list = SAFE_MALLOC(max_pids * sizeof(pid_t));
> +	memset(pid_list, 0, max_pids * sizeof(pid_t));
> +
> +	if (maxpercent) {
> +		/* set alloc_maxbytes to the extra amount we want to allocate */
> +		alloc_maxbytes = ((float)maxpercent / 100.00)
> +			* (sstats.mem_unit * total_free);
> +		tst_res(TINFO, "Filling up %d%% of free ram which is %llu kbytes",
> +			 maxpercent, alloc_maxbytes / 1024);
> +	}
> +	original_maxbytes = alloc_maxbytes;
>  }
>  
>  static void cleanup(void)
> @@ -66,25 +117,59 @@ static void cleanup(void)
>  	int i = 0;
>  
>  	while (pid_list[i] > 0) {
> -		kill(pid_list[i], SIGKILL);
> +		kill(pid_list[i], SIGCONT);

Is SIGCONT needed here? If everything goes OK, it's sent by mem_test().
If things go wrong, library kills process group with SIGKILL.

>  		i++;
>  	}
>  
> -	free(pid_list);
> +	if(pid_list)
> +		free(pid_list);
>  }
>  
> -int main(int argc, char *argv[])
> +static void handler(int sig LTP_ATTRIBUTE_UNUSED)
>  {
> -	int c;
> +        pid_count++;
> +}
> +
> +static void do_write_page(char *mem, int chunksize)
> +{
> +	int i;
> +
> +	for (i = 0; i < chunksize; i++)
> +		*(mem + i) = 'a';
> +}
> +
> +static void child_loop_alloc(void)
> +{
> +	unsigned long bytecount = (unsigned long)chunksize;
>  	char *mem;
> -	float percent;
> -	unsigned int maxpercent = 0, dowrite = 0, verbose = 0, j;
> -	unsigned long bytecount, alloc_bytes, max_pids;
> -	unsigned long long original_maxbytes, maxbytes = 0;
> -	unsigned long long pre_mem = 0, post_mem = 0;
> -	unsigned long long total_ram, total_free, D, C;
> -	int chunksize = 1024 * 1024;	/* one meg at a time by default */
> -	struct sysinfo sstats;
> +
> +	while (1) {
> +		mem = SAFE_MALLOC(chunksize);
> +		if (dowrite)
> +			do_write_page(mem, chunksize);
> +
> +		if (verbose)
> +			tst_res(TINFO,
> +				"child %d allocated %lu bytes chunksize is %d",
> +				getpid(), bytecount, chunksize);
> +		bytecount += chunksize;
> +		if (alloc_bytes && bytecount >= alloc_bytes)
> +			break;
> +	}
> +	if (dowrite)
> +		tst_res(TINFO, "... %lu bytes allocated and used in child %d",
> +				bytecount, getpid());
> +	else
> +		tst_res(TINFO, "... %lu bytes allocated only in child %d",
> +				bytecount, getpid());
> +
> +	kill(getppid(), SIGRTMIN);
> +	raise(SIGSTOP);
> +	exit(0);
> +}
> +
> +static void mem_test(void)
> +{
>  	int i, pid_cntr;
>  	pid_t pid;
>  	struct sigaction act;
> @@ -93,234 +178,110 @@ int main(int argc, char *argv[])
>  	act.sa_flags = 0;
>  	sigemptyset(&act.sa_mask);
>  	sigaction(SIGRTMIN, &act, 0);
> -	sigaction(SIGCHLD, &act, 0);
> -
> -	while ((c = getopt(argc, argv, "c:b:p:wvh")) != -1) {
> -		switch (c) {
> -		case 'c':
> -			chunksize = atoi(optarg);
> -			break;
> -		case 'b':
> -			if (maxpercent != 0)
> -				tst_brkm(TBROK, NULL,
> -					 "ERROR: -b option cannot be used with -p "
> -					 "option at the same time");
> -			maxbytes = atoll(optarg);
> -			break;
> -		case 'p':
> -			if (maxbytes != 0)
> -				tst_brkm(TBROK, NULL,
> -					 "ERROR: -p option cannot be used with -b "
> -					 "option at the same time");
> -			maxpercent = atoi(optarg);
> -			if (maxpercent <= 0)
> -				tst_brkm(TBROK, NULL,
> -					 "ERROR: -p option requires number greater "
> -					 "than 0");
> -			if (maxpercent > 99)
> -				tst_brkm(TBROK, NULL,
> -					 "ERROR: -p option cannot be greater than "
> -					 "99");
> -			break;
> -		case 'w':
> -			dowrite = 1;
> -			break;
> -		case 'v':
> -			verbose = 1;
> -			break;
> -		case 'h':
> -		default:
> -			printf
> -			    ("usage: %s [-c <bytes>] [-b <bytes>|-p <percent>] [-v]\n",
> -			     argv[0]);
> -			printf
> -			    ("\t-c <num>\tsize of chunk in bytes to malloc on each pass\n");
> -			printf
> -			    ("\t-b <bytes>\tmaximum number of bytes to allocate before
> stopping\n");
> -			printf
> -			    ("\t-p <bytes>\tpercent of total memory used at which the program
> stops\n");
> -			printf
> -			    ("\t-w\t\twrite to the memory after allocating\n");
> -			printf("\t-v\t\tverbose\n");
> -			printf("\t-h\t\tdisplay usage\n");
> -			exit(1);
> -		}
> -	}
> -
> -	sysinfo(&sstats);
> -	total_ram = sstats.totalram + sstats.totalswap;
> -	total_free = sstats.freeram + sstats.freeswap;
> -	/* Total Free Pre-Test RAM */
> -	pre_mem = sstats.mem_unit * total_free;
> -	max_pids = total_ram * sstats.mem_unit
> -		/ (unsigned long)FIVE_HUNDRED_MB + 10;
> -
> -	if ((pid_list = malloc(max_pids * sizeof(pid_t))) == NULL)
> -		tst_brkm(TBROK | TERRNO, NULL, "malloc failed.");
> -	memset(pid_list, 0, max_pids * sizeof(pid_t));
> -
> -	/* Currently used memory */
> -	C = sstats.mem_unit * (total_ram - total_free);
> -	tst_resm(TINFO, "Total memory already used on system = %llu kbytes",
> -		 C / 1024);
> -
> -	if (maxpercent) {
> -		percent = (float)maxpercent / 100.00;
> -
> -		/* Desired memory needed to reach maxpercent */
> -		D = percent * (sstats.mem_unit * total_ram);
> -		tst_resm(TINFO,
> -			 "Total memory used needed to reach maximum = %llu kbytes",
> -			 D / 1024);
> -
> -		/* Are we already using more than maxpercent? */
> -		if (C > D) {
> -			tst_resm(TFAIL,
> -				 "More memory than the maximum amount you specified "
> -				 " is already being used");
> -			free(pid_list);
> -			tst_exit();
> -		}
>  
> -		/* set maxbytes to the extra amount we want to allocate */
> -		maxbytes = D - C;
> -		tst_resm(TINFO, "Filling up %d%% of ram which is %llu kbytes",
> -			 maxpercent, maxbytes / 1024);
> -	}
> -	original_maxbytes = maxbytes;
>  	i = 0;
>  	pid_cntr = 0;
> -	pid = fork();
> -	if (pid < 0)
> -		tst_brkm(TBROK | TERRNO, cleanup, "fork failed");
> +	pid = SAFE_FORK();
>  	if (pid != 0) {
>  		pid_cntr++;
>  		pid_list[i] = pid;
>  	}
>  
> -#if defined (_s390_)		/* s390's 31bit addressing requires smaller chunks */
> -	while (pid != 0 && maxbytes > FIVE_HUNDRED_MB) {
> +#if defined (_s390_)	/* s390's 31bit addressing requires smaller chunks */

Can you also rework these ifdefs? It's appears, the same code is same
in each of them. The only difference is how much child allocates.

"alloc_bytes" could be a parameter for child_loop_alloc(), it doesn't
look like it needs to be global.

Regards,
Jan

> +	while (pid != 0 && alloc_maxbytes > FIVE_HUNDRED_MB) {
>  		i++;
>  		if (i >= max_pids)
> -			tst_brkm(TBROK, cleanup, "max_pids needs to be increased");
> -		maxbytes -= FIVE_HUNDRED_MB;
> -		pid = fork();
> -		if (pid < 0)
> -			tst_brkm(TBROK | TERRNO, cleanup, "fork failed");
> +			tst_brk(TBROK, "max_pids needs to be increased");
> +		alloc_maxbytes -= FIVE_HUNDRED_MB;
> +		pid = SAFE_FORK();
>  		if (pid != 0) {
>  			pid_cntr++;
>  			pid_list[i] = pid;
>  		}
>  	}
> -	if (maxbytes > FIVE_HUNDRED_MB)
> +	if (alloc_maxbytes > FIVE_HUNDRED_MB)
>  		alloc_bytes = FIVE_HUNDRED_MB;
>  	else
> -		alloc_bytes = (unsigned long)maxbytes;
> +		alloc_bytes = alloc_maxbytes;
>  
>  #elif __WORDSIZE == 32
> -	while (pid != 0 && maxbytes > ONE_GB) {
> +	while (pid != 0 && alloc_maxbytes > ONE_GB) {
>  		i++;
>  		if (i >= max_pids)
> -			tst_brkm(TBROK, cleanup, "max_pids needs to be increased");
> -		maxbytes -= ONE_GB;
> -		pid = fork();
> -		if (pid < 0)
> -		    tst_brkm(TBROK | TERRNO, cleanup, "fork failed");
> +			tst_brk(TBROK, "max_pids needs to be increased");
> +		allocmaxbytes -= ONE_GB;
> +		pid = SAFE_FORK();
>  		if (pid != 0) {
>  			pid_cntr++;
>  			pid_list[i] = pid;
>  		}
>  	}
> -	if (maxbytes > ONE_GB)
> +	if (alloc_maxbytes > ONE_GB)
>  		alloc_bytes = ONE_GB;
>  	else
> -		alloc_bytes = (unsigned long)maxbytes;
> +		alloc_bytes = alloc_maxbytes;
>  
>  #elif __WORDSIZE == 64
> -	while (pid != 0 && maxbytes > THREE_GB) {
> +	while (pid != 0 && alloc_maxbytes > THREE_GB) {
>  		i++;
>  		if (i >= max_pids)
> -			tst_brkm(TBROK, cleanup, "max_pids needs to be increased");
> -		maxbytes -= THREE_GB;
> -		pid = fork();
> -		if (pid < 0)
> -			tst_brkm(TBROK | TERRNO, cleanup, "fork failed");
> +			tst_brk(TBROK, "max_pids needs to be increased");
> +		alloc_maxbytes -= THREE_GB;
> +		pid = SAFE_FORK();
>  		if (pid != 0) {
>  			pid_cntr++;
>  			pid_list[i] = pid;
>  		}
>  	}
> -	alloc_bytes = MIN(THREE_GB, maxbytes);
> +	alloc_bytes = MIN(THREE_GB, alloc_maxbytes);
>  #endif
>  
> -	if (pid == 0) {
> -		bytecount = chunksize;
> -		while (1) {
> -			if ((mem = malloc(chunksize)) == NULL) {
> -				tst_resm(TBROK | TERRNO,
> -					 "stopped at %lu bytes", bytecount);
> -				free(pid_list);
> -				tst_exit();
> -			}
> -			if (dowrite)
> -				for (j = 0; j < chunksize; j++)
> -					*(mem + j) = 'a';
> -			if (verbose)
> -				tst_resm(TINFO,
> -					 "allocated %lu bytes chunksize is %d",
> -					 bytecount, chunksize);
> -			bytecount += chunksize;
> -			if (alloc_bytes && bytecount >= alloc_bytes)
> -				break;
> -		}
> -		if (dowrite)
> -			tst_resm(TINFO, "... %lu bytes allocated and used.",
> -				 bytecount);
> -		else
> -			tst_resm(TINFO, "... %lu bytes allocated only.",
> -				 bytecount);
> -		kill(getppid(), SIGRTMIN);
> -		while (1)
> -			sleep(1);
> -	} else {
> -		sysinfo(&sstats);
> -
> -		if (dowrite) {
> -			/* Total Free Post-Test RAM */
> -			post_mem =
> -			    (unsigned long long)sstats.mem_unit *
> -			    sstats.freeram;
> -			post_mem =
> -			    post_mem +
> -			    (unsigned long long)sstats.mem_unit *
> -			    sstats.freeswap;
> -
> -			while ((((unsigned long long)pre_mem - post_mem) <
> -				(unsigned long long)original_maxbytes) &&
> -			       pid_count < pid_cntr && !sigchld_count) {
> -				sleep(1);
> -				sysinfo(&sstats);
> -				post_mem =
> -				    (unsigned long long)sstats.mem_unit *
> -				    sstats.freeram;
> -				post_mem =
> -				    post_mem +
> -				    (unsigned long long)sstats.mem_unit *
> -				    sstats.freeswap;
> -			}
> +	if (pid == 0)
> +		child_loop_alloc();
> +
> +	/* waits in the loop for all children finish allocating*/
> +	while(pid_count < pid_cntr) {
> +		if (tst_timeout_remaining() < STOP_THRESHOLD) {
> +			tst_res(TWARN,
> +				"the remaininig time is not enough for testing");
> +
> +			break;
>  		}
>  
> -		if (sigchld_count) {
> -			tst_resm(TFAIL, "child process exited unexpectedly");
> -		} else if (dowrite) {
> -			tst_resm(TPASS, "%llu kbytes allocated and used.",
> -				 original_maxbytes / 1024);
> +		sleep(1);
> +	}
> +
> +	if (dowrite) {
> +		if(pid_count < pid_cntr) {
> +			tst_res(TFAIL, "kbytes allocated and used less than expected %llu",
> +					original_maxbytes / 1024);
> +
>  		} else {
> -			tst_resm(TPASS, "%llu kbytes allocated only.",
> -				 original_maxbytes / 1024);
> +			tst_res(TPASS, "%llu kbytes allocated and used",
> +					original_maxbytes / 1024);
>  		}
> +	} else {
> +		if(pid_count < pid_cntr) {
> +			tst_res(TFAIL, "kbytes allocated less than expected %llu",
> +					original_maxbytes / 1024);
> +		} else {
> +			tst_res(TPASS, "%llu kbytes allocated only",
> +					original_maxbytes / 1024);
> +		}
> +	}
>  
> +	i = 0;
> +	while (pid_list[i] > 0) {
> +		TST_PROCESS_STATE_WAIT(pid_list[i], 'T');
> +		kill(pid_list[i], SIGCONT);
> +		i++;
>  	}
> -	cleanup();
> -	tst_exit();
>  }
> +
> +static struct tst_test test = {
> +	.forks_child = 1,
> +	.options = mtest_options,
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.test_all = mem_test,
> +};
> --
> 2.20.1
> 
> 

      reply	other threads:[~2019-03-07  9:57 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-03-06  9:51 [LTP] [PATCH v2] mm: rewrite mtest01 with new API Li Wang
2019-03-07  9:57 ` Jan Stancek [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=997663519.5833721.1551952635690.JavaMail.zimbra@redhat.com \
    --to=jstancek@redhat.com \
    --cc=ltp@lists.linux.it \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.